diff --git a/.dockerignore b/.dockerignore index d3be7426b..72c49dfbb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ docker-compose.yml LICENSE contracts/ docs/ -erc7824-docs/ sdk/ts/ test/ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4e19d7391..b93cc0637 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,4 @@ # CODEOWNERS: https://help.github.com/articles/about-codeowners/ # Yellow Network - Research and Development -* @erc7824/yellow-network +* @alessio @dimast-x @nksazonov @philanton @ihsraham diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f3edff838..65d4f6ba0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,6 @@ updates: - package-ecosystem: "npm" # See documentation for possible values directories: - "/sdk/ts" - - "/erc7824-docs" - "/sdk/compat" - "/test/integration" - "/sdk/ts/examples/*app*" diff --git a/.github/workflows/main-pr.yml b/.github/workflows/main-pr.yml index 6a7afc431..96d4b3ab3 100644 --- a/.github/workflows/main-pr.yml +++ b/.github/workflows/main-pr.yml @@ -57,22 +57,3 @@ jobs: build-args: | VERSION=${{ steps.sha.outputs.short_sha }} - # TODO: Enable this job if docs preview are needed (do not forget to provide GITHUB_TOKEN and GH access to receive preview URLs). - # https://stackoverflow.com/questions/75514653/firebase-action-hosting-deploy-fails-with-requesterror-resource-not-accessible - - # build-and-preview-docs-firebase: - # name: Deploy to Firebase Hosting on PR - # if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - - # - run: npm install && npm run build - # working-directory: erc7824-docs - - # - uses: FirebaseExtended/action-hosting-deploy@v0 - # with: - # # repoToken: ${{ secrets.GITHUB_TOKEN }} - # firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_ERC7824 }} - # projectId: erc7824 - # entryPoint: ./erc7824-docs diff --git a/.github/workflows/main-push.yml b/.github/workflows/main-push.yml index 370ce4fd9..901393272 100644 --- a/.github/workflows/main-push.yml +++ b/.github/workflows/main-push.yml @@ -117,19 +117,3 @@ jobs: # ${{github.event.head_commit.message}} # SLACK_FOOTER: 'Nitrolite CI/CD Pipeline' - # build-and-deploy-docs-firebase: - # name: Deploy to Firebase Hosting on merge - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v6 - - # - run: npm install && npm run build - # working-directory: erc7824-docs - - # - uses: FirebaseExtended/action-hosting-deploy@v0 - # with: - # # repoToken: ${{ secrets.GITHUB_TOKEN }} - # firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_ERC7824 }} - # channelId: live - # projectId: erc7824 - # entryPoint: ./erc7824-docs diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml index a302e7266..55ce9da2a 100644 --- a/.github/workflows/test-go.yml +++ b/.github/workflows/test-go.yml @@ -36,4 +36,4 @@ jobs: - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - slug: erc7824/nitrolite + slug: layer-3/nitrolite diff --git a/.github/workflows/v1-push.yml b/.github/workflows/v1-push.yml index 532debf23..93af694f7 100644 --- a/.github/workflows/v1-push.yml +++ b/.github/workflows/v1-push.yml @@ -160,20 +160,3 @@ jobs: # ⚠️ RC build or deployment was cancelled! # ${{github.event.head_commit.message}} # SLACK_FOOTER: 'Nitrolite CI/CD Pipeline' - - # build-and-deploy-docs-firebase: - # name: Deploy to Firebase Hosting on merge - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 - - # - run: npm install && npm run build - # working-directory: erc7824-docs - - # - uses: FirebaseExtended/action-hosting-deploy@v0 - # with: - # # repoToken: ${{ secrets.GITHUB_TOKEN }} - # firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_ERC7824 }} - # channelId: live - # projectId: erc7824 - # entryPoint: ./erc7824-docs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..daa7def30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +.prettierrc +.prettierignore +.vscode/ +.prettierrc +.prettierignore +.vscode/ +.idea/ +*.swp +.DS_Store +contracts/foundry.toml +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..bdb00ee51 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,22 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 120, + "semi": true, + "singleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "always", + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 160, + "tabWidth": 4, + "useTabs": false, + "bracketSpacing": false + } + } + ], + "plugins": ["prettier-plugin-solidity"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..efb5435dc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "[solidity]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false + }, + "prettier.documentSelectors": ["**/*.sol"], + "prettier.enable": true +} diff --git a/LICENSE b/LICENSE index 36f6d597a..20b1a81cf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025, 2026 erc7824 +Copyright (c) 2025, 2026 layer-3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 62256e191..627a21c2b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Release Policy](https://img.shields.io/badge/release%20policy-v1.0-blue)](https://github.com/layer-3/release-process/blob/master/README.md) -[![codecov](https://codecov.io/github/erc7824/nitrolite/graph/badge.svg?token=XASM4CIEFO)](https://codecov.io/github/erc7824/nitrolite) -[![Go Reference](https://pkg.go.dev/badge/github.com/erc7824/nitrolite.svg)](https://pkg.go.dev/github.com/erc7824/nitrolite) -[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/erc7824/nitrolite)](https://github.com/erc7824/nitrolite/releases) +[![codecov](https://codecov.io/github/layer-3/nitrolite/graph/badge.svg?token=XASM4CIEFO)](https://codecov.io/github/layer-3/nitrolite) +[![Go Reference](https://pkg.go.dev/badge/github.com/layer-3/nitrolite.svg)](https://pkg.go.dev/github.com/layer-3/nitrolite) +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/layer-3/nitrolite)](https://github.com/layer-3/nitrolite/releases) # Nitrolite: State Channel Framework @@ -87,7 +87,7 @@ See the [Clearnode Documentation](/clearnode/README.md) for more details. The official Go SDK for building performant backend integrations or CLI tools. ```bash -go get github.com/erc7824/nitrolite/sdk/go +go get github.com/layer-3/nitrolite/sdk/go ``` See [SDK Go README](/sdk/go/README.md). @@ -96,7 +96,7 @@ See [SDK Go README](/sdk/go/README.md). The official TypeScript SDK for web-based applications. ```bash -npm install @erc7824/nitrolite +npm install @layer-3/nitrolite ``` See [SDK TS README](/sdk/ts/README.md). diff --git a/cerebro/README.md b/cerebro/README.md index bb330feed..e1f2622ac 100644 --- a/cerebro/README.md +++ b/cerebro/README.md @@ -12,7 +12,7 @@ go build -o clearnode-cli Or install directly: ```bash -go install github.com/erc7824/nitrolite/sdk/go/examples/cli@latest +go install github.com/layer-3/nitrolite/sdk/go/examples/cli@latest ``` ## Quick Start diff --git a/cerebro/commands.go b/cerebro/commands.go index 822ccb14e..a3b15fbdf 100644 --- a/cerebro/commands.go +++ b/cerebro/commands.go @@ -5,18 +5,30 @@ import ( "crypto/ecdsa" "crypto/rand" "fmt" + "os" "strconv" "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" + "golang.org/x/term" ) +// readSecure reads a line from stdin without echo. Works under go-prompt's raw mode. +func readSecure() string { + bytes, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() // newline after hidden input + if err != nil { + return "" + } + return strings.TrimSpace(string(bytes)) +} + // ============================================================================ // Help & Config // ============================================================================ @@ -26,15 +38,27 @@ func (o *Operator) showHelp() { Clearnode CLI - SDK Development Tool ===================================== -SETUP COMMANDS - help Display this help message - config Display current configuration - wallet Display wallet address - import wallet Configure wallet (import or generate) - import rpc Configure blockchain RPC endpoint - -HIGH-LEVEL OPERATIONS (Smart Client) - token-balance Check on-chain token balance for your wallet +CONFIGURATION + config Display current configuration + config wallet Display wallet address + config wallet import Import existing private key + config wallet generate Generate new wallet + config wallet export Export private key to file + config rpc import Configure blockchain RPC endpoint + config node Show node info + config node set-ws-url Set clearnode WebSocket URL + config node set-home-blockchain Set home blockchain for channels + config session-key Show current session key info + config session-key generate Generate new session key + config session-key import Import existing session key + config session-key clear Clear session key, revert to default signer + config session-key register-channel-key Register channel session key + config session-key channel-keys List active channel session keys + config session-key register-app-key [apps] [sessions] Register app session key + config session-key app-keys List active app session keys + +OPERATIONS + token-balance Check on-chain token balance approve Approve token spending for deposits deposit Deposit to channel (auto-create if needed) withdraw Withdraw from channel @@ -43,52 +67,47 @@ HIGH-LEVEL OPERATIONS (Smart Client) close-channel Close home channel on-chain checkpoint Submit latest state on-chain -NODE INFORMATION (Base Client) +QUERIES ping Test node connection - node info Get node configuration chains List supported blockchains assets [chain_id] List supported assets (optionally filter by chain) - -USER QUERIES (Base Client) balances [wallet] Get user balances (defaults to configured wallet) - transactions [wallet] Get transaction history (defaults to configured wallet) - -LOW-LEVEL STATE MANAGEMENT (Base Client) - state [wallet] Get latest state (wallet defaults to configured) - home-channel [wallet] Get home channel (wallet defaults to configured) + transactions [wallet] Get transaction history + action-allowances [wallet] Get action allowances + state [wallet] Get latest state + home-channel [wallet] Get home channel escrow-channel Get escrow channel by ID -LOW-LEVEL APP SESSIONS (Base Client) - app-sessions List app sessions +APP REGISTRY + app-info Show application details + my-apps List your registered applications + register-app [no-approval] Register a new application + app-sessions List app sessions -SESSION KEY MANAGEMENT - generate-session-key Generate or import session key (stores locally) - session-key Show current session key info - clear-session-key Clear session key, revert to default wallet signer - create-channel-session-key Register channel session key (auto-activates if stored) - channel-session-keys List active channel session keys - create-app-session-key [app_ids] [session_ids] Register app session key (IDs: comma-separated) - app-session-keys List active app session keys +SECURITY TOKEN OPERATIONS + security-token approve Approve security token spending + security-token balance [wallet] Check escrowed security token balance + security-token escrow [target_address] Escrow security tokens + security-token initiate-withdrawal Start unlock period + security-token cancel-withdrawal Cancel unlock and re-lock + security-token withdraw Withdraw unlocked security tokens OTHER + help Display this help message exit Exit the CLI EXAMPLES - import wallet - import rpc 80002 https://polygon-amoy.g.alchemy.com/v2/KEY + config wallet import + config rpc import 80002 https://polygon-amoy.g.alchemy.com/v2/KEY + config session-key generate + config session-key register-channel-key 0xabcd... 24 usdc,weth approve 80002 usdc 1000000 deposit 80002 usdc 100 transfer 0x1234... usdc 50 - balances # Uses configured wallet - balances 0x1234... # Query specific wallet - state usdc # Get state for USDC - chains - generate-session-key # Step 1: generate/import - create-channel-session-key 0xabcd... 24 usdc,weth # Step 2: register + activate - create-app-session-key 0xabcd... 24 app1,app2`) + balances`) } -func (o *Operator) showConfig(ctx context.Context) { +func (o *Operator) showConfig() { fmt.Println("Current Configuration") fmt.Println("=====================") @@ -136,14 +155,15 @@ func (o *Operator) showConfig(ctx context.Context) { } } - // Node info - nodeConfig, err := o.client.GetConfig(ctx) - if err == nil { - fmt.Printf("\nNode Info\n") - fmt.Printf(" Address: %s\n", nodeConfig.NodeAddress) - fmt.Printf(" Version: %s\n", nodeConfig.NodeVersion) - fmt.Printf(" Chains: %d\n", len(nodeConfig.Blockchains)) - } + // Node connection + fmt.Printf("Node: %s\n", o.wsURL) + + fmt.Println() + fmt.Println("Commands:") + fmt.Println(" config wallet Wallet management") + fmt.Println(" config rpc import Configure blockchain RPC") + fmt.Println(" config node Node info and connection") + fmt.Println(" config session-key Session key management") } // ============================================================================ @@ -155,7 +175,7 @@ func (o *Operator) showWallet(_ context.Context) { privateKey, err := o.store.GetPrivateKey() if err != nil { fmt.Println("ERROR: No wallet configured") - fmt.Println("INFO: Use 'import wallet' to configure wallet") + fmt.Println("INFO: Use 'config wallet import' to configure wallet") return } @@ -173,101 +193,81 @@ func (o *Operator) showWallet(_ context.Context) { fmt.Printf("Address: %s\n", address) } -// ============================================================================ -// Import Commands -// ============================================================================ - -func (o *Operator) importWallet(_ context.Context) { - fmt.Println("Wallet Configuration") - fmt.Println("====================") - fmt.Println() - fmt.Println("Choose an option:") - fmt.Println(" 1. Import existing private key") - fmt.Println(" 2. Generate new wallet") - fmt.Println() - fmt.Print("Enter choice (1 or 2): ") +func (o *Operator) exportWallet(exportPath string) { + privateKey, err := o.store.GetPrivateKey() + if err != nil { + fmt.Println("ERROR: No wallet configured") + return + } - var choice string - fmt.Scanln(&choice) - choice = strings.TrimSpace(choice) + if err := os.WriteFile(exportPath, []byte(privateKey+"\n"), 0600); err != nil { + fmt.Printf("ERROR: Failed to export wallet: %v\n", err) + return + } - var privateKey string - var signer sign.Signer - var err error + fmt.Printf("SUCCESS: Private key exported to %s\n", exportPath) + fmt.Println("WARNING: Keep this file secure and do not share it with anyone.") +} - switch choice { - case "1": - // Import existing key - fmt.Println() - fmt.Println("Import Existing Wallet") - fmt.Print("Enter private key (with or without 0x prefix): ") - fmt.Scanln(&privateKey) +// ============================================================================ +// Import Commands +// ============================================================================ - privateKey = strings.TrimSpace(privateKey) - if privateKey == "" { - fmt.Println("ERROR: Private key cannot be empty") - return - } +func (o *Operator) importWallet() { + fmt.Print("Enter private key (with or without 0x prefix): ") + privateKey := readSecure() + if privateKey == "" { + fmt.Println("ERROR: Private key cannot be empty") + return + } - // Validate by creating signer - signer, err = sign.NewEthereumRawSigner(privateKey) - if err != nil { - fmt.Printf("ERROR: Invalid private key: %v\n", err) - return - } + signer, err := sign.NewEthereumRawSigner(privateKey) + if err != nil { + fmt.Printf("ERROR: Invalid private key: %v\n", err) + return + } - case "2": - // Generate new wallet - fmt.Println() - fmt.Println("Generate New Wallet") - privateKey, err = generatePrivateKey() - if err != nil { - fmt.Printf("ERROR: Failed to generate private key: %v\n", err) - return - } + if err := o.store.SetPrivateKey(privateKey); err != nil { + fmt.Printf("ERROR: Failed to save private key: %v\n", err) + return + } - signer, err = sign.NewEthereumRawSigner(privateKey) - if err != nil { - fmt.Printf("ERROR: Failed to create signer: %v\n", err) - return - } + fmt.Printf("SUCCESS: Wallet configured\n") + fmt.Printf("Address: %s\n", signer.PublicKey().Address().String()) - fmt.Println() - fmt.Println("WARNING: Save your private key securely!") - fmt.Println("=========================================") - fmt.Printf("Private Key: %s\n", privateKey) - fmt.Println("=========================================") - fmt.Println() - fmt.Print("Type 'I have saved my private key' to continue: ") + fmt.Println("Reconnecting...") + if err := o.reconnect(); err != nil { + fmt.Printf("WARNING: Failed to reconnect: %v\n", err) + fmt.Println("INFO: Restart the CLI to apply changes.") + } +} - var confirmation string - fmt.Scanln(&confirmation) - // Read the full line - if confirmation == "" { - fmt.Println("ERROR: You must confirm that you saved the private key") - return - } +func (o *Operator) generateWallet() { + privateKey, err := generatePrivateKey() + if err != nil { + fmt.Printf("ERROR: Failed to generate private key: %v\n", err) + return + } - default: - fmt.Println("ERROR: Invalid choice") + signer, err := sign.NewEthereumRawSigner(privateKey) + if err != nil { + fmt.Printf("ERROR: Failed to create signer: %v\n", err) return } - // Save to storage if err := o.store.SetPrivateKey(privateKey); err != nil { fmt.Printf("ERROR: Failed to save private key: %v\n", err) return } - fmt.Printf("SUCCESS: Wallet configured successfully\n") + fmt.Printf("SUCCESS: New wallet generated\n") fmt.Printf("Address: %s\n", signer.PublicKey().Address().String()) + fmt.Println("IMPORTANT: Run 'config wallet export' to save your private key to a file.") - if choice == "2" { - fmt.Println() - fmt.Println("Security Recommendations:") - fmt.Println(" - Store your private key in a secure location") - fmt.Println(" - Never share your private key with anyone") - fmt.Println(" - Consider using a hardware wallet for large amounts") + fmt.Println("Reconnecting...") + if err := o.reconnect(); err != nil { + fmt.Printf("WARNING: Failed to reconnect: %v\n", err) + fmt.Println("INFO: Restart the CLI to apply changes.") } } @@ -282,9 +282,14 @@ func (o *Operator) importRPC(_ context.Context, chainIDStr, rpcURL string) { fmt.Printf("ERROR: Failed to save RPC: %v\n", err) return } - // TODO: add to SDK Client dynamically fmt.Printf("SUCCESS: RPC configured for chain %d\n", chainID) + + fmt.Println("Reconnecting...") + if err := o.reconnect(); err != nil { + fmt.Printf("WARNING: Failed to reconnect: %v\n", err) + fmt.Println("INFO: Restart the CLI to apply changes.") + } } func (o *Operator) setHomeBlockchain(_ context.Context, asset, chainIDStr string) { @@ -339,7 +344,7 @@ func (o *Operator) tokenBalance(ctx context.Context, chainIDStr, asset string) { wallet := o.getImportedWalletAddress() if wallet == "" { - fmt.Println("ERROR: No wallet configured. Use 'import wallet' first.") + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") return } @@ -483,6 +488,7 @@ func (o *Operator) nodeInfo(ctx context.Context) { fmt.Println("Node Information") fmt.Println("================") + fmt.Printf("WS URL: %s\n", o.wsURL) fmt.Printf("Address: %s\n", config.NodeAddress) fmt.Printf("Version: %s\n", config.NodeVersion) fmt.Printf("Chains: %d\n", len(config.Blockchains)) @@ -504,10 +510,30 @@ func (o *Operator) nodeInfo(ctx context.Context) { fmt.Println("\nSupported Blockchains:") for _, bc := range config.Blockchains { fmt.Printf(" - %s (ID: %d)\n", bc.Name, bc.ID) - fmt.Printf(" Contract: %s\n", bc.ChannelHubAddress) + fmt.Printf(" Channel Hub: %s\n", bc.ChannelHubAddress) + if bc.LockingContractAddress != "" { + fmt.Printf(" Locking: %s\n", bc.LockingContractAddress) + } } } +func (o *Operator) setWSURL(wsURL string) { + if err := o.store.SetWSURL(wsURL); err != nil { + fmt.Printf("ERROR: Failed to save WebSocket URL: %v\n", err) + return + } + + o.wsURL = wsURL + fmt.Printf("SUCCESS: WebSocket URL set to %s\n", wsURL) + fmt.Println("INFO: Reconnecting...") + if err := o.reconnect(); err != nil { + fmt.Printf("ERROR: Failed to reconnect: %v\n", err) + fmt.Println("INFO: URL saved. Restart the CLI to connect.") + return + } + fmt.Println("SUCCESS: Connected to new node") +} + func (o *Operator) listChains(ctx context.Context) { chains, err := o.client.GetBlockchains(ctx) if err != nil { @@ -714,6 +740,90 @@ func (o *Operator) listTransactions(ctx context.Context, wallet string) { } } +func (o *Operator) getActionAllowances(ctx context.Context, wallet string) { + allowances, err := o.client.GetActionAllowances(ctx, wallet) + if err != nil { + fmt.Printf("ERROR: Failed to get action allowances: %v\n", err) + return + } + + fmt.Printf("Action Allowances for %s\n", wallet) + fmt.Println("========================================") + if len(allowances) == 0 { + fmt.Println("No action allowances found") + return + } + + for _, a := range allowances { + fmt.Printf("- %s\n", a.GatedAction) + fmt.Printf(" Window: %s\n", a.TimeWindow) + fmt.Printf(" Used: %d / %d\n", a.Used, a.Allowance) + remaining := uint64(0) + if a.Allowance > a.Used { + remaining = a.Allowance - a.Used + } + fmt.Printf(" Remaining: %d\n", remaining) + } +} + +// ============================================================================ +// App Registry +// ============================================================================ + +func (o *Operator) getApps(ctx context.Context, appID *string, ownerWallet *string) { + fmt.Println("Fetching registered applications...") + + apps, _, err := o.client.GetApps(ctx, &sdk.GetAppsOptions{ + AppID: appID, + OwnerWallet: ownerWallet, + }) + if err != nil { + fmt.Printf("ERROR: Failed to get apps: %v\n", err) + return + } + + if len(apps) == 0 { + fmt.Println("No applications found.") + return + } + + fmt.Printf("Found %d application(s):\n\n", len(apps)) + for _, a := range apps { + fmt.Printf(" App ID: %s\n", a.App.ID) + fmt.Printf(" Owner: %s\n", a.App.OwnerWallet) + fmt.Printf(" Version: %d\n", a.App.Version) + if a.App.CreationApprovalNotRequired { + fmt.Println(" Approval: Not required") + } else { + fmt.Println(" Approval: Required") + } + if a.App.Metadata != "" { + fmt.Printf(" Metadata: %s\n", a.App.Metadata) + } + fmt.Printf(" Created: %s\n", a.CreatedAt.Format("2006-01-02 15:04:05")) + fmt.Printf(" Updated: %s\n", a.UpdatedAt.Format("2006-01-02 15:04:05")) + fmt.Println() + } +} + +func (o *Operator) registerApp(ctx context.Context, appID, metadata string, creationApprovalNotRequired bool) { + fmt.Printf("Registering application: %s...\n", appID) + + err := o.client.RegisterApp(ctx, appID, metadata, creationApprovalNotRequired) + if err != nil { + fmt.Printf("ERROR: Failed to register app: %v\n", err) + return + } + + fmt.Println("SUCCESS: Application registered") + fmt.Printf(" App ID: %s\n", appID) + if creationApprovalNotRequired { + fmt.Println(" Approval: Not required for session creation") + } else { + fmt.Println(" Approval: Required for session creation") + } +} + // ============================================================================ // Low-Level State Management (Base Client) // ============================================================================ @@ -774,10 +884,10 @@ func (o *Operator) listAppSessions(ctx context.Context, wallet string) { for _, session := range sessions { fmt.Printf("\n- Session %s\n", session.AppSessionID) fmt.Printf(" Version: %d\n", session.Version) - fmt.Printf(" Nonce: %d\n", session.Nonce) - fmt.Printf(" Quorum: %d\n", session.Quorum) + fmt.Printf(" Nonce: %d\n", session.AppDefinition.Nonce) + fmt.Printf(" Quorum: %d\n", session.AppDefinition.Quorum) fmt.Printf(" Closed: %v\n", session.IsClosed) - fmt.Printf(" Participants: %d\n", len(session.Participants)) + fmt.Printf(" Participants: %d\n", len(session.AppDefinition.Participants)) fmt.Printf(" Allocations: %d\n", len(session.Allocations)) } } @@ -786,50 +896,34 @@ func (o *Operator) listAppSessions(ctx context.Context, wallet string) { // Session Key Management // ============================================================================ -func (o *Operator) generateSessionKey(_ context.Context) { - fmt.Println("Session Key Setup") - fmt.Println("=================") - fmt.Println() - fmt.Println("Choose an option:") - fmt.Println(" 1. Generate new session key") - fmt.Println(" 2. Import existing private key") - fmt.Println() - fmt.Print("Enter choice (1 or 2): ") - - var choice string - fmt.Scanln(&choice) - choice = strings.TrimSpace(choice) +func (o *Operator) generateSessionKey() { + privateKeyHex, err := generatePrivateKey() + if err != nil { + fmt.Printf("ERROR: Failed to generate session key: %v\n", err) + return + } - var privateKeyHex string - var err error + o.storeSessionKey(privateKeyHex) +} - switch choice { - case "1": - privateKeyHex, err = generatePrivateKey() - if err != nil { - fmt.Printf("ERROR: Failed to generate session key: %v\n", err) - return - } - case "2": - fmt.Print("Enter session key private key (hex): ") - fmt.Scanln(&privateKeyHex) - privateKeyHex = strings.TrimSpace(privateKeyHex) - if privateKeyHex == "" { - fmt.Println("ERROR: Private key cannot be empty") - return - } - default: - fmt.Println("ERROR: Invalid choice") +func (o *Operator) importSessionKey() { + fmt.Print("Enter session key private key (hex): ") + privateKeyHex := readSecure() + if privateKeyHex == "" { + fmt.Println("ERROR: Private key cannot be empty") return } + o.storeSessionKey(privateKeyHex) +} + +func (o *Operator) storeSessionKey(privateKeyHex string) { signer, err := sign.NewEthereumRawSigner(privateKeyHex) if err != nil { fmt.Printf("ERROR: Invalid private key: %v\n", err) return } - // Store the session key private key locally (no metadata yet — will be set on registration) if err := o.store.SetSessionKeyPrivateKey(privateKeyHex); err != nil { fmt.Printf("ERROR: Failed to store session key: %v\n", err) return @@ -837,17 +931,11 @@ func (o *Operator) generateSessionKey(_ context.Context) { address := signer.PublicKey().Address().String() - fmt.Println() fmt.Println("SUCCESS: Session key stored locally") fmt.Printf(" Address: %s\n", address) - if choice == "1" { - fmt.Printf(" Private Key: %s\n", privateKeyHex) - fmt.Println() - fmt.Println("WARNING: Save the private key securely!") - } fmt.Println() fmt.Println("Next step: Register it on the clearnode with:") - fmt.Printf(" create-channel-session-key %s \n", address) + fmt.Printf(" config session-key register-channel-key %s \n", address) } func (o *Operator) showSessionKey() { @@ -857,7 +945,7 @@ func (o *Operator) showSessionKey() { pk, pkErr := o.store.GetSessionKeyPrivateKey() if pkErr != nil { fmt.Println("No session key configured") - fmt.Println("INFO: Use 'generate-session-key' to create one.") + fmt.Println("INFO: Use 'config session-key generate' to create one.") return } signer, sigErr := sign.NewEthereumRawSigner(pk) @@ -871,7 +959,7 @@ func (o *Operator) showSessionKey() { fmt.Println("Status: Stored locally (not yet registered on clearnode)") fmt.Println() fmt.Println("Next step: Register it with:") - fmt.Printf(" create-channel-session-key %s \n", signer.PublicKey().Address().String()) + fmt.Printf(" config session-key register-channel-key %s \n", signer.PublicKey().Address().String()) return } @@ -918,7 +1006,7 @@ func (o *Operator) createChannelSessionKey(ctx context.Context, sessionKeyAddr, wallet := o.getImportedWalletAddress() if wallet == "" { - fmt.Println("ERROR: No wallet configured. Use 'import wallet' first.") + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") return } @@ -1043,7 +1131,7 @@ func (o *Operator) createAppSessionKey(ctx context.Context, sessionKeyAddr, expi wallet := o.getImportedWalletAddress() if wallet == "" { - fmt.Println("ERROR: No wallet configured. Use 'import wallet' first.") + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") return } @@ -1122,6 +1210,145 @@ func (o *Operator) listAppSessionKeys(ctx context.Context, wallet string) { } } +// ============================================================================ +// Security Token Operations +// ============================================================================ + +func (o *Operator) escrowSecurityTokens(ctx context.Context, chainIDStr, targetAddress, amountStr string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + amount, err := o.parseAmount(amountStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + // Default target to own wallet if not specified + if targetAddress == "" { + targetAddress = o.getImportedWalletAddress() + if targetAddress == "" { + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") + return + } + fmt.Printf("INFO: Using configured wallet as target: %s\n", targetAddress) + } + + fmt.Printf("Escrowing %s security tokens for %s on chain %d...\n", amount.String(), targetAddress, chainID) + + txHash, err := o.client.EscrowSecurityTokens(ctx, targetAddress, chainID, amount) + if err != nil { + fmt.Printf("ERROR: Escrow failed: %v\n", err) + return + } + + fmt.Println("SUCCESS: Security tokens escrowed") + fmt.Printf("Transaction Hash: %s\n", txHash) +} + +func (o *Operator) initiateSecurityWithdrawal(ctx context.Context, chainIDStr string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + fmt.Printf("Initiating security tokens withdrawal on chain %d...\n", chainID) + + txHash, err := o.client.InitiateSecurityTokensWithdrawal(ctx, chainID) + if err != nil { + fmt.Printf("ERROR: Initiate withdrawal failed: %v\n", err) + return + } + + fmt.Println("SUCCESS: Security tokens withdrawal initiated") + fmt.Printf("Transaction Hash: %s\n", txHash) +} + +func (o *Operator) cancelSecurityWithdrawal(ctx context.Context, chainIDStr string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + fmt.Printf("Cancelling security tokens withdrawal on chain %d...\n", chainID) + + txHash, err := o.client.CancelSecurityTokensWithdrawal(ctx, chainID) + if err != nil { + fmt.Printf("ERROR: Cancel withdrawal failed: %v\n", err) + return + } + + fmt.Println("SUCCESS: Security tokens withdrawal cancelled (re-locked)") + fmt.Printf("Transaction Hash: %s\n", txHash) +} + +func (o *Operator) withdrawSecurityTokens(ctx context.Context, chainIDStr, destination string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + fmt.Printf("Withdrawing security tokens to %s on chain %d...\n", destination, chainID) + + txHash, err := o.client.WithdrawSecurityTokens(ctx, chainID, destination) + if err != nil { + fmt.Printf("ERROR: Withdraw security tokens failed: %v\n", err) + return + } + + fmt.Println("SUCCESS: Security tokens withdrawn") + fmt.Printf("Transaction Hash: %s\n", txHash) +} + +func (o *Operator) approveSecurityToken(ctx context.Context, chainIDStr, amountStr string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + amount, err := o.parseAmount(amountStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + fmt.Printf("Approving %s security tokens on chain %d...\n", amount.String(), chainID) + + txHash, err := o.client.ApproveSecurityToken(ctx, chainID, amount) + if err != nil { + fmt.Printf("ERROR: Approve security token failed: %v\n", err) + return + } + + fmt.Println("SUCCESS: Security token spending approved") + fmt.Printf("Transaction Hash: %s\n", txHash) +} + +func (o *Operator) securityBalance(ctx context.Context, chainIDStr, wallet string) { + chainID, err := o.parseChainID(chainIDStr) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + return + } + + fmt.Printf("Querying security token balance for %s on chain %d...\n", wallet, chainID) + + balance, err := o.client.GetLockedBalance(ctx, chainID, wallet) + if err != nil { + fmt.Printf("ERROR: Failed to get security token balance: %v\n", err) + return + } + + fmt.Printf("Security token balance: %s\n", balance.String()) +} + // ============================================================================ // Helper Methods // ============================================================================ diff --git a/cerebro/main.go b/cerebro/main.go index 52f907d3a..03b7c0a74 100644 --- a/cerebro/main.go +++ b/cerebro/main.go @@ -12,14 +12,11 @@ import ( ) func main() { + const defaultWSURL = "wss://clearnode-sandbox.yellow.org/v1/ws" + log.SetFlags(0) log.SetPrefix("clearnode-cli: ") log.SetOutput(os.Stderr) - if len(os.Args) < 2 { - log.Fatalf("Usage: clearnode-cli \nExample: clearnode-cli wss://clearnode.example.com/ws") - } - - wsURL := os.Args[1] // Get config directory configDir := os.Getenv("CLEARNODE_CLI_CONFIG_DIR") @@ -42,8 +39,18 @@ func main() { log.Fatalf("failed to initialize storage: %v", err) } + // Determine WebSocket URL: CLI arg > stored > default + var wsURL string + if len(os.Args) >= 2 { + wsURL = os.Args[1] + } else if stored, err := store.GetWSURL(); err == nil { + wsURL = stored + } else { + wsURL = defaultWSURL + } + // Create operator - operator, err := NewOperator(wsURL, store) + operator, err := NewOperator(wsURL, configDir, store) if err != nil { log.Fatalf("failed to create operator: %v", err) } diff --git a/cerebro/operator.go b/cerebro/operator.go index 47d7bf738..8b2a08835 100644 --- a/cerebro/operator.go +++ b/cerebro/operator.go @@ -8,24 +8,26 @@ import ( "time" "github.com/c-bata/go-prompt" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" "github.com/shopspring/decimal" ) type Operator struct { - wsURL string - store *Storage - client *sdk.Client - exitCh chan struct{} + wsURL string + configDir string + store *Storage + client *sdk.Client + exitCh chan struct{} } -func NewOperator(wsURL string, store *Storage) (*Operator, error) { +func NewOperator(wsURL, configDir string, store *Storage) (*Operator, error) { op := &Operator{ - wsURL: wsURL, - store: store, - exitCh: make(chan struct{}), + wsURL: wsURL, + configDir: configDir, + store: store, + exitCh: make(chan struct{}), } if err := op.connect(); err != nil { @@ -69,10 +71,29 @@ func (o *Operator) buildStateSigner(walletPrivateKey string) (core.ChannelSigner } // connect creates the SDK client with the appropriate signer. +// If no wallet is configured, a new one is automatically generated. func (o *Operator) connect() error { privateKey, err := o.store.GetPrivateKey() if err != nil { - return fmt.Errorf("no wallet imported (use 'import wallet' first): %w", err) + // Auto-generate a new wallet for first-time users + privateKey, err = generatePrivateKey() + if err != nil { + return fmt.Errorf("failed to generate wallet: %w", err) + } + if err := o.store.SetPrivateKey(privateKey); err != nil { + return fmt.Errorf("failed to save generated wallet: %w", err) + } + signer, err := sign.NewEthereumRawSigner(privateKey) + if err != nil { + return fmt.Errorf("failed to create signer: %w", err) + } + fmt.Println() + fmt.Println("Welcome! No wallet imported. A new wallet has been generated for you.") + fmt.Printf("Address: %s\n", signer.PublicKey().Address().String()) + fmt.Println() + fmt.Println("IMPORTANT: Run 'config wallet export' to save your private key to a file.") + fmt.Println("INFO: You can import a different wallet anytime with 'config wallet import'.") + fmt.Println() } stateSigner, err := o.buildStateSigner(privateKey) @@ -144,10 +165,7 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { return []prompt.Suggest{ // Setup {Text: "help", Description: "Show help information"}, - {Text: "config", Description: "Show current configuration"}, - {Text: "wallet", Description: "Show wallet address"}, - {Text: "import", Description: "Import wallet or blockchain RPC"}, - {Text: "set-home-blockchain", Description: "Set home blockchain for channels"}, + {Text: "config", Description: "Configuration (wallet, rpc, node, session-key)"}, // High-level operations {Text: "token-balance", Description: "Check on-chain token balance"}, @@ -161,49 +179,57 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { // Node information {Text: "ping", Description: "Test node connection"}, - {Text: "node", Description: "Get node information"}, {Text: "chains", Description: "List supported blockchains"}, {Text: "assets", Description: "List supported assets"}, // User queries {Text: "balances", Description: "Get user balances"}, {Text: "transactions", Description: "Get transaction history"}, + {Text: "action-allowances", Description: "Get action allowances"}, // State management {Text: "state", Description: "Get latest state"}, {Text: "home-channel", Description: "Get home channel"}, {Text: "escrow-channel", Description: "Get escrow channel"}, + // App registry + {Text: "app-info", Description: "Show application details"}, + {Text: "my-apps", Description: "List your registered applications"}, + {Text: "register-app", Description: "Register a new application"}, + // App sessions (Base Client - Low-level) {Text: "app-sessions", Description: "List app sessions"}, - // Session key management - {Text: "generate-session-key", Description: "Generate or import session key (stores locally)"}, - {Text: "session-key", Description: "Show current session key info"}, - {Text: "clear-session-key", Description: "Clear session key, revert to default signer"}, - {Text: "create-channel-session-key", Description: "Register channel session key"}, - {Text: "channel-session-keys", Description: "List active channel session keys"}, - {Text: "create-app-session-key", Description: "Register app session key"}, - {Text: "app-session-keys", Description: "List active app session keys"}, + // Security token operations + {Text: "security-token", Description: "Security token operations"}, - {Text: "exit", Description: "Exit the CLI"}, +{Text: "exit", Description: "Exit the CLI"}, } } // Second level if len(args) < 3 { switch args[0] { - case "import": + case "config": return []prompt.Suggest{ - {Text: "wallet", Description: "Import private key for signing"}, - {Text: "rpc", Description: "Import blockchain RPC URL"}, + {Text: "wallet", Description: "Wallet management"}, + {Text: "rpc", Description: "RPC management"}, + {Text: "node", Description: "Node info and connection"}, + {Text: "session-key", Description: "Session key management"}, } - case "set-home-blockchain", "close-channel", "acknowledge", "checkpoint": - // set-home-blockchain , others take + case "close-channel", "acknowledge", "checkpoint": return o.getAssetSuggestions() case "token-balance", "approve", "deposit", "withdraw": - // token-balance , approve/deposit/withdraw return o.getChainSuggestions() + case "security-token": + return []prompt.Suggest{ + {Text: "approve", Description: "Approve security token spending"}, + {Text: "balance", Description: "Check escrowed security token balance"}, + {Text: "escrow", Description: "Escrow security tokens"}, + {Text: "initiate-withdrawal", Description: "Start unlock period"}, + {Text: "cancel-withdrawal", Description: "Cancel unlock and re-lock"}, + {Text: "withdraw", Description: "Withdraw unlocked security tokens"}, + } case "state", "home-channel": // state [wallet] , home-channel [wallet] // Suggest asset first (common case), wallet can be typed manually @@ -211,27 +237,44 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { case "transfer": // transfer return o.getWalletSuggestion() - case "balances", "transactions": + case "balances", "transactions", "action-allowances": return o.getWalletSuggestion() case "assets": return o.getChainSuggestions() - case "node": - return []prompt.Suggest{ - {Text: "info", Description: "Get node configuration"}, - } } } // Third level if len(args) < 4 { switch args[0] { - case "import": - if args[1] == "rpc" { - return o.getChainSuggestions() + case "config": + switch args[1] { + case "wallet": + return []prompt.Suggest{ + {Text: "import", Description: "Import existing private key"}, + {Text: "generate", Description: "Generate new wallet"}, + {Text: "export", Description: "Export private key to file"}, + } + case "rpc": + return []prompt.Suggest{ + {Text: "import", Description: "Import blockchain RPC URL"}, + } + case "node": + return []prompt.Suggest{ + {Text: "set-ws-url", Description: "Set clearnode WebSocket URL"}, + {Text: "set-home-blockchain", Description: "Set home blockchain for channels"}, + } + case "session-key": + return []prompt.Suggest{ + {Text: "generate", Description: "Generate new session key"}, + {Text: "import", Description: "Import existing session key"}, + {Text: "clear", Description: "Clear session key, revert to default signer"}, + {Text: "register-channel-key", Description: "Register channel session key"}, + {Text: "channel-keys", Description: "List active channel session keys"}, + {Text: "register-app-key", Description: "Register app session key"}, + {Text: "app-keys", Description: "List active app session keys"}, + } } - case "set-home-blockchain": - // set-home-blockchain - return o.getChainSuggestions() case "token-balance": // token-balance return o.getAssetSuggestions() @@ -244,6 +287,9 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { case "state", "home-channel": // state [wallet] — if wallet was explicitly provided, suggest asset return o.getAssetSuggestions() + case "security-token": + // security-token ... + return o.getChainSuggestions() case "escrow-channel": // Escrow channel ID (no suggestion) return nil @@ -253,9 +299,24 @@ func (o *Operator) complete(d prompt.Document) []prompt.Suggest { // Fourth level if len(args) < 5 { switch args[0] { - case "create-channel-session-key": - // Fourth arg is assets (comma-separated) - return o.getAssetSuggestions() + case "config": + switch args[1] { + case "rpc": + if args[2] == "import" { + return o.getChainSuggestions() + } + case "node": + if args[2] == "set-home-blockchain" { + return o.getAssetSuggestions() + } + } + } + } + + // Fifth level + if len(args) < 6 { + if args[0] == "config" && args[1] == "node" && args[2] == "set-home-blockchain" { + return o.getChainSuggestions() } } @@ -275,33 +336,125 @@ func (o *Operator) Execute(s string) { case "help": o.showHelp() case "config": - o.showConfig(ctx) - case "wallet": - o.showWallet(ctx) - case "import": if len(args) < 2 { - fmt.Println("ERROR: Usage: import ...") + o.showConfig() return } switch args[1] { case "wallet": - o.importWallet(ctx) + if len(args) < 3 { + o.showWallet(ctx) + return + } + switch args[2] { + case "import": + o.importWallet() + case "generate": + o.generateWallet() + case "export": + if len(args) < 4 { + fmt.Println("ERROR: Usage: config wallet export ") + return + } + o.exportWallet(args[3]) + default: + fmt.Printf("ERROR: Unknown wallet command: %s\n", args[2]) + fmt.Println("Usage: config wallet [import|generate|export]") + } case "rpc": - if len(args) < 4 { - fmt.Println("ERROR: Usage: import rpc ") + if len(args) < 3 { + fmt.Println("ERROR: Usage: config rpc import ") return } - o.importRPC(ctx, args[2], args[3]) + switch args[2] { + case "import": + if len(args) < 5 { + fmt.Println("ERROR: Usage: config rpc import ") + return + } + o.importRPC(ctx, args[3], args[4]) + default: + fmt.Printf("ERROR: Unknown rpc command: %s\n", args[2]) + fmt.Println("Usage: config rpc [import]") + } + case "node": + if len(args) < 3 { + o.nodeInfo(ctx) + return + } + switch args[2] { + case "set-ws-url": + if len(args) < 4 { + fmt.Println("ERROR: Usage: config node set-ws-url ") + return + } + o.setWSURL(args[3]) + case "set-home-blockchain": + if len(args) < 5 { + fmt.Println("ERROR: Usage: config node set-home-blockchain ") + return + } + o.setHomeBlockchain(ctx, args[3], args[4]) + default: + fmt.Printf("ERROR: Unknown node command: %s\n", args[2]) + fmt.Println("Usage: config node [set-ws-url|set-home-blockchain]") + } + case "session-key": + if len(args) < 3 { + o.showSessionKey() + return + } + switch args[2] { + case "generate": + o.generateSessionKey() + case "import": + o.importSessionKey() + case "clear": + o.clearSessionKey() + case "register-channel-key": + if len(args) < 6 { + fmt.Println("ERROR: Usage: config session-key register-channel-key ") + fmt.Println("INFO: Assets are comma-separated, e.g. usdc,weth") + return + } + o.createChannelSessionKey(ctx, args[3], args[4], args[5]) + case "channel-keys": + wallet := o.getImportedWalletAddress() + if wallet == "" { + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") + return + } + o.listChannelSessionKeys(ctx, wallet) + case "register-app-key": + if len(args) < 5 { + fmt.Println("ERROR: Usage: config session-key register-app-key [app_ids] [session_ids]") + fmt.Println("INFO: IDs are comma-separated. app_ids and session_ids are optional.") + return + } + appIDs := "" + sessionIDs := "" + if len(args) >= 6 { + appIDs = args[5] + } + if len(args) >= 7 { + sessionIDs = args[6] + } + o.createAppSessionKey(ctx, args[3], args[4], appIDs, sessionIDs) + case "app-keys": + wallet := o.getImportedWalletAddress() + if wallet == "" { + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") + return + } + o.listAppSessionKeys(ctx, wallet) + default: + fmt.Printf("ERROR: Unknown session-key command: %s\n", args[2]) + fmt.Println("Usage: config session-key [generate|import|clear|register-channel-key|channel-keys|register-app-key|app-keys]") + } default: - fmt.Printf("ERROR: Unknown import type: %s\n", args[1]) + fmt.Printf("ERROR: Unknown config command: %s\n", args[1]) + fmt.Println("Usage: config [wallet|rpc|node|session-key]") } - - case "set-home-blockchain": - if len(args) < 3 { - fmt.Println("ERROR: Usage: set-home-blockchain ") - return - } - o.setHomeBlockchain(ctx, args[1], args[2]) // High-level operations case "token-balance": if len(args) < 3 { @@ -355,10 +508,6 @@ func (o *Operator) Execute(s string) { // Node information case "ping": o.ping(ctx) - case "node": - if len(args) < 2 || args[1] == "info" { - o.nodeInfo(ctx) - } case "chains": o.listChains(ctx) case "assets": @@ -378,7 +527,7 @@ func (o *Operator) Execute(s string) { wallet = o.getImportedWalletAddress() if wallet == "" { fmt.Println("ERROR: Usage: balances ") - fmt.Println("INFO: No wallet configured. Use 'import wallet' first or specify a wallet address.") + fmt.Println("INFO: No wallet configured. Use 'config wallet import' first or specify a wallet address.") return } fmt.Printf("INFO: Using configured wallet: %s\n", wallet) @@ -393,7 +542,7 @@ func (o *Operator) Execute(s string) { wallet = o.getImportedWalletAddress() if wallet == "" { fmt.Println("ERROR: Usage: transactions ") - fmt.Println("INFO: No wallet configured. Use 'import wallet' first or specify a wallet address.") + fmt.Println("INFO: No wallet configured. Use 'config wallet import' first or specify a wallet address.") return } fmt.Printf("INFO: Using configured wallet: %s\n", wallet) @@ -412,7 +561,7 @@ func (o *Operator) Execute(s string) { wallet = o.getImportedWalletAddress() if wallet == "" { fmt.Println("ERROR: Usage: state ") - fmt.Println("INFO: No wallet configured. Use 'import wallet' first or specify a wallet address.") + fmt.Println("INFO: No wallet configured. Use 'config wallet import' first or specify a wallet address.") return } asset = args[1] @@ -434,7 +583,7 @@ func (o *Operator) Execute(s string) { wallet = o.getImportedWalletAddress() if wallet == "" { fmt.Println("ERROR: Usage: home-channel ") - fmt.Println("INFO: No wallet configured. Use 'import wallet' first or specify a wallet address.") + fmt.Println("INFO: No wallet configured. Use 'config wallet import' first or specify a wallet address.") return } asset = args[1] @@ -452,54 +601,117 @@ func (o *Operator) Execute(s string) { } o.getEscrowChannel(ctx, args[1]) - // App sessions - case "app-sessions": - wallet := o.getImportedWalletAddress() - o.listAppSessions(ctx, wallet) - - // Session key management - case "generate-session-key": - o.generateSessionKey(ctx) - case "session-key": - o.showSessionKey() - case "clear-session-key": - o.clearSessionKey() - case "create-channel-session-key": - if len(args) < 4 { - fmt.Println("ERROR: Usage: create-channel-session-key ") - fmt.Println("INFO: Assets are comma-separated, e.g. usdc,weth") + // App registry + case "app-info": + if len(args) < 2 { + fmt.Println("ERROR: Usage: app-info ") return } - o.createChannelSessionKey(ctx, args[1], args[2], args[3]) - case "channel-session-keys": + o.getApps(ctx, &args[1], nil) + + case "my-apps": wallet := o.getImportedWalletAddress() if wallet == "" { - fmt.Println("ERROR: No wallet configured. Use 'import wallet' first.") + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first.") return } - o.listChannelSessionKeys(ctx, wallet) - case "create-app-session-key": - if len(args) < 3 { - fmt.Println("ERROR: Usage: create-app-session-key [app_ids] [session_ids]") - fmt.Println("INFO: IDs are comma-separated. app_ids and session_ids are optional.") + o.getApps(ctx, nil, &wallet) + + case "register-app": + if len(args) < 2 { + fmt.Println("ERROR: Usage: register-app [no-approval]") + fmt.Println("INFO: Pass 'no-approval' as second arg to allow session creation without owner approval") return } - appIDs := "" - sessionIDs := "" - if len(args) >= 4 { - appIDs = args[3] - } - if len(args) >= 5 { - sessionIDs = args[4] + noApproval := len(args) >= 3 && args[2] == "no-approval" + o.registerApp(ctx, args[1], "", noApproval) + + // User action allowances + case "action-allowances": + wallet := "" + if len(args) >= 2 { + wallet = args[1] + } else { + wallet = o.getImportedWalletAddress() + if wallet == "" { + fmt.Println("ERROR: Usage: action-allowances ") + fmt.Println("INFO: No wallet configured. Use 'config wallet import' first or specify a wallet address.") + return + } + fmt.Printf("INFO: Using configured wallet: %s\n", wallet) } - o.createAppSessionKey(ctx, args[1], args[2], appIDs, sessionIDs) - case "app-session-keys": + o.getActionAllowances(ctx, wallet) + + // App sessions + case "app-sessions": wallet := o.getImportedWalletAddress() - if wallet == "" { - fmt.Println("ERROR: No wallet configured. Use 'import wallet' first.") + o.listAppSessions(ctx, wallet) + + + // Security token operations + case "security-token": + if len(args) < 2 { + fmt.Println("ERROR: Usage: security-token ...") + fmt.Println("Commands: approve, balance, escrow, initiate-withdrawal, cancel-withdrawal, withdraw") return } - o.listAppSessionKeys(ctx, wallet) + switch args[1] { + case "approve": + if len(args) < 4 { + fmt.Println("ERROR: Usage: security-token approve ") + return + } + o.approveSecurityToken(ctx, args[2], args[3]) + case "escrow": + if len(args) < 4 { + fmt.Println("ERROR: Usage: security-token escrow [target_address] ") + fmt.Println("INFO: If target_address is omitted, your own wallet is used.") + return + } + if len(args) >= 5 { + o.escrowSecurityTokens(ctx, args[2], args[3], args[4]) + } else { + o.escrowSecurityTokens(ctx, args[2], "", args[3]) + } + case "initiate-withdrawal": + if len(args) < 3 { + fmt.Println("ERROR: Usage: security-token initiate-withdrawal ") + return + } + o.initiateSecurityWithdrawal(ctx, args[2]) + case "cancel-withdrawal": + if len(args) < 3 { + fmt.Println("ERROR: Usage: security-token cancel-withdrawal ") + return + } + o.cancelSecurityWithdrawal(ctx, args[2]) + case "withdraw": + if len(args) < 4 { + fmt.Println("ERROR: Usage: security-token withdraw ") + return + } + o.withdrawSecurityTokens(ctx, args[2], args[3]) + case "balance": + if len(args) < 3 { + fmt.Println("ERROR: Usage: security-token balance [wallet_address]") + return + } + wallet := "" + if len(args) >= 4 { + wallet = args[3] + } else { + wallet = o.getImportedWalletAddress() + if wallet == "" { + fmt.Println("ERROR: No wallet configured. Use 'config wallet import' first or specify a wallet address.") + return + } + fmt.Printf("INFO: Using configured wallet: %s\n", wallet) + } + o.securityBalance(ctx, args[2], wallet) + default: + fmt.Printf("ERROR: Unknown security-token command: %s\n", args[1]) + fmt.Println("Commands: approve, balance, escrow, initiate-withdrawal, cancel-withdrawal, withdraw") + } case "exit": fmt.Println("Exiting...") diff --git a/cerebro/operator_test.go b/cerebro/operator_test.go index d11decb39..53e338d3e 100644 --- a/cerebro/operator_test.go +++ b/cerebro/operator_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/erc7824/nitrolite/pkg/core" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -152,7 +152,7 @@ func TestOperator_Connect_Failure(t *testing.T) { require.NoError(t, err) // Attempt to connect to a non-existent server - _, err = NewOperator("ws://localhost:12345/nonexistent", s) + _, err = NewOperator("ws://localhost:12345/nonexistent", t.TempDir(), s) assert.Error(t, err) assert.Contains(t, err.Error(), "failed to connect to clearnode") } diff --git a/cerebro/storage.go b/cerebro/storage.go index 1165d64f0..2939a2cbb 100644 --- a/cerebro/storage.go +++ b/cerebro/storage.go @@ -35,6 +35,20 @@ func NewStorage(path string) (*Storage, error) { return &Storage{db: db}, nil } +func (s *Storage) SetWSURL(wsURL string) error { + _, err := s.db.Exec("INSERT OR REPLACE INTO config (key, value) VALUES ('ws_url', ?)", wsURL) + return err +} + +func (s *Storage) GetWSURL() (string, error) { + var wsURL string + err := s.db.QueryRow("SELECT value FROM config WHERE key = 'ws_url'").Scan(&wsURL) + if err == sql.ErrNoRows { + return "", fmt.Errorf("no WebSocket URL configured") + } + return wsURL, err +} + func (s *Storage) SetPrivateKey(privateKey string) error { _, err := s.db.Exec("INSERT OR REPLACE INTO config (key, value) VALUES ('private_key', ?)", privateKey) return err diff --git a/clearnode/action_gateway/action_gateway.go b/clearnode/action_gateway/action_gateway.go new file mode 100644 index 000000000..1eecbf519 --- /dev/null +++ b/clearnode/action_gateway/action_gateway.go @@ -0,0 +1,209 @@ +package action_gateway + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" + "go.yaml.in/yaml/v2" +) + +const ( + actionGatewayFileName = "action_gateway.yaml" + defaultTimeWindow = 24 * time.Hour +) + +type ActionLimitConfig struct { + LevelStepTokens decimal.Decimal `yaml:"level_step_tokens"` + AppCost decimal.Decimal `yaml:"app_cost"` + ActionGates map[core.GatedAction]ActionGateConfig `yaml:"action_gates"` +} + +type ActionGateConfig struct { + FreeActionsAllowance uint64 `yaml:"free_actions_allowance"` + IncreasePerLevel uint64 `yaml:"increase_per_level"` +} + +type ActionGateway struct { + cfg ActionLimitConfig +} + +func NewActionGateway(cfg ActionLimitConfig) (*ActionGateway, error) { + if !cfg.LevelStepTokens.IsPositive() { + return nil, errors.New("LevelStepTokens must be greater than zero") + } + if !cfg.AppCost.IsPositive() { + return nil, errors.New("AppCost must be greater than zero") + } + + seenIDs := make(map[uint8]core.GatedAction, len(cfg.ActionGates)) + for action := range cfg.ActionGates { + id := action.ID() + if id == 0 { + return nil, fmt.Errorf("unknown action_gates key: %q", action) + } + if prev, exists := seenIDs[id]; exists && prev != action { + return nil, fmt.Errorf("duplicate gated action id %d for %q and %q", id, prev, action) + } + seenIDs[id] = action + } + + return &ActionGateway{ + cfg: cfg, + }, nil +} + +func NewActionGatewayFromYaml(configDirPath string) (*ActionGateway, error) { + assetsPath := filepath.Join(configDirPath, actionGatewayFileName) + f, err := os.Open(assetsPath) + if err != nil { + return nil, err + } + defer f.Close() + + var cfg ActionLimitConfig + if err := yaml.NewDecoder(f).Decode(&cfg); err != nil { + return nil, err + } + + return NewActionGateway(cfg) +} + +type Store interface { + // GetAppCount returns the total number of applications owned by a specific wallet. + GetAppCount(ownerWallet string) (uint64, error) + + // GetTotalUserStaked returns the total staked amount for a user across all blockchains. + GetTotalUserStaked(wallet string) (decimal.Decimal, error) + + // RecordAction inserts a new action log entry for a user. + RecordAction(wallet string, gatedAction core.GatedAction) error + + // GetUserActionCount returns the number of actions matching the given wallet and gated action + // within the specified time window (counting backwards from now). + GetUserActionCount(wallet string, gatedAction core.GatedAction, window time.Duration) (uint64, error) + + // GetUserActionCounts returns a map of gated actions to their respective counts for a user within the specified time window. + GetUserActionCounts(userWallet string, window time.Duration) (map[core.GatedAction]uint64, error) +} + +func (a *ActionGateway) AllowAction(tx Store, userAddress string, gatedAction core.GatedAction) error { + if _, ok := a.cfg.ActionGates[gatedAction]; !ok { + return nil + } + + stakedTokens, err := tx.GetTotalUserStaked(userAddress) + if err != nil { + return fmt.Errorf("failed to get user staked amount: %w", err) + } + + remainingStaked := stakedTokens + if stakedTokens.IsPositive() { + appCount, err := tx.GetAppCount(userAddress) + if err != nil { + return fmt.Errorf("failed to get app count: %w", err) + } + + maintenanceCost := a.cfg.AppCost.Mul(decimal.NewFromUint64(appCount)) + remainingStaked = stakedTokens.Sub(maintenanceCost) + } + + allowance := a.stakedTo24hActionsAllowance(gatedAction, remainingStaked) + + usedCount, err := tx.GetUserActionCount(userAddress, gatedAction, defaultTimeWindow) + if err != nil { + return fmt.Errorf("failed to get user action count: %w", err) + } + + if usedCount >= allowance { + return fmt.Errorf("action %s limit reached: used %d of %d allowed in 24h", gatedAction, usedCount, allowance) + } + + if err := tx.RecordAction(userAddress, gatedAction); err != nil { + return fmt.Errorf("failed to record action: %w", err) + } + + return nil +} + +func (v *ActionGateway) AllowAppRegistration(tx Store, userAddress string) error { + stakedTokens, err := tx.GetTotalUserStaked(userAddress) + if err != nil { + return fmt.Errorf("failed to get user staked amount: %w", err) + } + if stakedTokens.IsZero() { + return errors.New("cannot register an app with zero staked tokens") + } + + appCount, err := tx.GetAppCount(userAddress) + if err != nil { + return fmt.Errorf("failed to get app count: %w", err) + } + + maintenanceCost := v.cfg.AppCost.Mul(decimal.NewFromUint64(appCount + 1)) + if stakedTokens.LessThan(maintenanceCost) { + return fmt.Errorf("not enough staked tokens to register a new app: staked %s, required %s", stakedTokens.String(), maintenanceCost.String()) + } + + return nil +} + +// stakedTo24hActionsAllowance returns the number of executions allowed in 24 hours for a specific gated action. +func (a *ActionGateway) stakedTo24hActionsAllowance(gatedAction core.GatedAction, remainingStaked decimal.Decimal) uint64 { + actionLinitsConfig, ok := a.cfg.ActionGates[gatedAction] + if !ok { + return 0 + } + actionAllowance := uint64(actionLinitsConfig.FreeActionsAllowance) + if remainingStaked.IsPositive() { + levels := uint64(remainingStaked.Div(a.cfg.LevelStepTokens).IntPart()) + actionAllowance += actionLinitsConfig.IncreasePerLevel * levels + } + return actionAllowance +} + +// GetUserAllowances returns user allowance for every gated action. +func (a *ActionGateway) GetUserAllowances(tx Store, userAddress string) ([]core.ActionAllowance, error) { + stakedTokens, err := tx.GetTotalUserStaked(userAddress) + if err != nil { + return nil, fmt.Errorf("failed to get user staked amount: %w", err) + } + + actionCounts, err := tx.GetUserActionCounts(userAddress, defaultTimeWindow) + if err != nil { + return nil, err + } + + remainingStaked := stakedTokens + if stakedTokens.IsPositive() { + appCount, err := tx.GetAppCount(userAddress) + if err != nil { + return nil, fmt.Errorf("failed to get app count: %w", err) + } + + maintenanceCost := a.cfg.AppCost.Mul(decimal.NewFromUint64(appCount)) + remainingStaked = stakedTokens.Sub(maintenanceCost) + } + + timeWindowStr := defaultTimeWindow.String() + result := make([]core.ActionAllowance, 0, len(a.cfg.ActionGates)) + for action := range a.cfg.ActionGates { + result = append(result, core.ActionAllowance{ + GatedAction: action, + TimeWindow: timeWindowStr, + Allowance: a.stakedTo24hActionsAllowance(action, remainingStaked), + Used: actionCounts[action], + }) + } + + slices.SortFunc(result, func(a, b core.ActionAllowance) int { + return int(a.GatedAction.ID()) - int(b.GatedAction.ID()) + }) + + return result, nil +} diff --git a/clearnode/action_gateway/action_gateway_test.go b/clearnode/action_gateway/action_gateway_test.go new file mode 100644 index 000000000..dad63ad5a --- /dev/null +++ b/clearnode/action_gateway/action_gateway_test.go @@ -0,0 +1,356 @@ +package action_gateway + +import ( + "errors" + "testing" + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// mockAVStore implements AVStore for unit tests. +type mockAVStore struct { + totalStaked decimal.Decimal + stakedErr error + appCount uint64 + appCountErr error + actionCount uint64 + actionErr error + actionCounts map[core.GatedAction]uint64 + actionCSErr error + recordedActions []core.GatedAction + recordErr error +} + +func (m *mockAVStore) GetTotalUserStaked(string) (decimal.Decimal, error) { + return m.totalStaked, m.stakedErr +} + +func (m *mockAVStore) GetAppCount(string) (uint64, error) { + return m.appCount, m.appCountErr +} + +func (m *mockAVStore) GetUserActionCount(string, core.GatedAction, time.Duration) (uint64, error) { + return m.actionCount, m.actionErr +} + +func (m *mockAVStore) GetUserActionCounts(string, time.Duration) (map[core.GatedAction]uint64, error) { + return m.actionCounts, m.actionCSErr +} + +func (m *mockAVStore) RecordAction(_ string, action core.GatedAction) error { + m.recordedActions = append(m.recordedActions, action) + return m.recordErr +} + +func defaultConfig() ActionLimitConfig { + return ActionLimitConfig{ + LevelStepTokens: decimal.NewFromInt(100), + AppCost: decimal.NewFromInt(50), + ActionGates: map[core.GatedAction]ActionGateConfig{ + core.GatedActionTransfer: {FreeActionsAllowance: 5, IncreasePerLevel: 10}, + }, + } +} + +func mustNewGateway(t *testing.T, cfg ActionLimitConfig) *ActionGateway { + t.Helper() + gw, err := NewActionGateway(cfg) + require.NoError(t, err) + return gw +} + +// --- NewActionGateway --- + +func TestNewActionGateway(t *testing.T) { + t.Run("valid config", func(t *testing.T) { + gw, err := NewActionGateway(defaultConfig()) + require.NoError(t, err) + assert.NotNil(t, gw) + }) + + t.Run("zero LevelStepTokens", func(t *testing.T) { + cfg := defaultConfig() + cfg.LevelStepTokens = decimal.Zero + _, err := NewActionGateway(cfg) + assert.Error(t, err) + assert.Contains(t, err.Error(), "LevelStepTokens") + }) + + t.Run("zero AppCost", func(t *testing.T) { + cfg := defaultConfig() + cfg.AppCost = decimal.Zero + _, err := NewActionGateway(cfg) + assert.Error(t, err) + assert.Contains(t, err.Error(), "AppCost") + }) +} + +// --- stakedTo24hActionsAllowance --- + +func TestStakedTo24hActionsAllowance(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + + t.Run("unknown action returns 0", func(t *testing.T) { + assert.Equal(t, uint64(0), gw.stakedTo24hActionsAllowance("unknown_action", decimal.NewFromInt(1000))) + }) + + t.Run("zero staked returns free allowance only", func(t *testing.T) { + assert.Equal(t, uint64(5), gw.stakedTo24hActionsAllowance(core.GatedActionTransfer, decimal.Zero)) + }) + + t.Run("negative staked returns free allowance only", func(t *testing.T) { + assert.Equal(t, uint64(5), gw.stakedTo24hActionsAllowance(core.GatedActionTransfer, decimal.NewFromInt(-100))) + }) + + t.Run("positive staked adds levels", func(t *testing.T) { + // 250 tokens / 100 step = 2 levels -> 5 + 2*10 = 25 + assert.Equal(t, uint64(25), gw.stakedTo24hActionsAllowance(core.GatedActionTransfer, decimal.NewFromInt(250))) + }) + + t.Run("partial level truncated", func(t *testing.T) { + // 199 tokens / 100 step = 1 level -> 5 + 1*10 = 15 + assert.Equal(t, uint64(15), gw.stakedTo24hActionsAllowance(core.GatedActionTransfer, decimal.NewFromInt(199))) + }) +} + +// --- AllowAction --- + +func TestAllowAction(t *testing.T) { + t.Run("allowed with free allowance, zero staked", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCount: 0, + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + require.NoError(t, err) + assert.Equal(t, []core.GatedAction{core.GatedActionTransfer}, store.recordedActions) + }) + + t.Run("allowed with staked tokens and apps", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + // 300 staked - 2 apps * 50 cost = 200 remaining -> 2 levels -> 5 + 20 = 25 + store := &mockAVStore{ + totalStaked: decimal.NewFromInt(300), + appCount: 2, + actionCount: 24, // under 25 + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + require.NoError(t, err) + }) + + t.Run("rejected at limit", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCount: 5, // equals free allowance + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.Error(t, err) + assert.Contains(t, err.Error(), "limit reached") + }) + + t.Run("rejected over limit", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCount: 10, + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.Error(t, err) + }) + + t.Run("unknown gated action returns nil without store calls", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + stakedErr: errors.New("should not be called"), + } + err := gw.AllowAction(store, "0xuser", "nonexistent") + require.NoError(t, err) + assert.Empty(t, store.recordedActions) + }) + + t.Run("staked error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{stakedErr: errors.New("db down")} + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.ErrorContains(t, err, "db down") + }) + + t.Run("app count error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.NewFromInt(100), + appCountErr: errors.New("db down"), + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.ErrorContains(t, err, "db down") + }) + + t.Run("action count error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionErr: errors.New("db down"), + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.ErrorContains(t, err, "db down") + }) + + t.Run("record error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCount: 0, + recordErr: errors.New("write fail"), + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + assert.ErrorContains(t, err, "write fail") + }) + + t.Run("skips GetAppCount when staked is zero", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + // If GetAppCount were called it would return an error + store := &mockAVStore{ + totalStaked: decimal.Zero, + appCountErr: errors.New("should not be called"), + actionCount: 0, + } + err := gw.AllowAction(store, "0xuser", core.GatedActionTransfer) + require.NoError(t, err) + }) +} + +// --- AllowAppRegistration --- + +func TestAllowAppRegistration(t *testing.T) { + t.Run("allowed with enough stake", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + // 0 existing apps, cost for 1 = 50, staked = 100 + store := &mockAVStore{totalStaked: decimal.NewFromInt(100), appCount: 0} + err := gw.AllowAppRegistration(store, "0xuser") + require.NoError(t, err) + }) + + t.Run("allowed exact cost", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + // 1 existing app, cost for 2 = 100, staked = 100 + store := &mockAVStore{totalStaked: decimal.NewFromInt(100), appCount: 1} + err := gw.AllowAppRegistration(store, "0xuser") + require.NoError(t, err) + }) + + t.Run("rejected not enough stake", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + // 1 existing app, cost for 2 = 100, staked = 99 + store := &mockAVStore{totalStaked: decimal.NewFromInt(99), appCount: 1} + err := gw.AllowAppRegistration(store, "0xuser") + assert.Error(t, err) + assert.Contains(t, err.Error(), "not enough staked tokens") + }) + + t.Run("rejected zero staked", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{totalStaked: decimal.Zero} + err := gw.AllowAppRegistration(store, "0xuser") + assert.Error(t, err) + assert.Contains(t, err.Error(), "zero staked tokens") + }) +} + +// --- GetUserAllowances --- + +func TestGetUserAllowances(t *testing.T) { + multiActionConfig := ActionLimitConfig{ + LevelStepTokens: decimal.NewFromInt(100), + AppCost: decimal.NewFromInt(50), + ActionGates: map[core.GatedAction]ActionGateConfig{ + core.GatedActionTransfer: {FreeActionsAllowance: 5, IncreasePerLevel: 10}, + core.GatedActionAppSessionOperation: {FreeActionsAllowance: 20, IncreasePerLevel: 5}, + }, + } + + t.Run("zero staked returns free allowances", func(t *testing.T) { + gw := mustNewGateway(t, multiActionConfig) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCounts: map[core.GatedAction]uint64{core.GatedActionTransfer: 3}, + } + result, err := gw.GetUserAllowances(store, "0xuser") + require.NoError(t, err) + require.Len(t, result, 2) + + // Results should be sorted by GatedAction ID + assert.Equal(t, core.GatedActionTransfer, result[0].GatedAction) + assert.Equal(t, uint64(5), result[0].Allowance) + assert.Equal(t, uint64(3), result[0].Used) + + assert.Equal(t, core.GatedActionAppSessionOperation, result[1].GatedAction) + assert.Equal(t, uint64(20), result[1].Allowance) + assert.Equal(t, uint64(0), result[1].Used) + }) + + t.Run("staked tokens increase allowances", func(t *testing.T) { + gw := mustNewGateway(t, multiActionConfig) + // 500 staked - 2 apps * 50 = 400 remaining -> 4 levels + store := &mockAVStore{ + totalStaked: decimal.NewFromInt(500), + appCount: 2, + actionCounts: map[core.GatedAction]uint64{}, + } + result, err := gw.GetUserAllowances(store, "0xuser") + require.NoError(t, err) + + // Transfer: 5 + 4*10 = 45 + assert.Equal(t, uint64(45), result[0].Allowance) + // AppSessionOperation: 20 + 4*5 = 40 + assert.Equal(t, uint64(40), result[1].Allowance) + }) + + t.Run("maintenance exceeds staked gives free allowance", func(t *testing.T) { + gw := mustNewGateway(t, multiActionConfig) + // 50 staked - 2 apps * 50 = -50 remaining + store := &mockAVStore{ + totalStaked: decimal.NewFromInt(50), + appCount: 2, + actionCounts: map[core.GatedAction]uint64{}, + } + result, err := gw.GetUserAllowances(store, "0xuser") + require.NoError(t, err) + + assert.Equal(t, uint64(5), result[0].Allowance) + assert.Equal(t, uint64(20), result[1].Allowance) + }) + + t.Run("time window is set", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCounts: map[core.GatedAction]uint64{}, + } + result, err := gw.GetUserAllowances(store, "0xuser") + require.NoError(t, err) + assert.Equal(t, "24h0m0s", result[0].TimeWindow) + }) + + t.Run("staked error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{stakedErr: errors.New("db down")} + _, err := gw.GetUserAllowances(store, "0xuser") + assert.ErrorContains(t, err, "db down") + }) + + t.Run("action counts error propagated", func(t *testing.T) { + gw := mustNewGateway(t, defaultConfig()) + store := &mockAVStore{ + totalStaked: decimal.Zero, + actionCSErr: errors.New("db down"), + } + _, err := gw.GetUserAllowances(store, "0xuser") + assert.ErrorContains(t, err, "db down") + }) +} diff --git a/clearnode/api/app_session_v1/create_app_session.go b/clearnode/api/app_session_v1/create_app_session.go index d2e6043c8..1db8d74dd 100644 --- a/clearnode/api/app_session_v1/create_app_session.go +++ b/clearnode/api/app_session_v1/create_app_session.go @@ -4,9 +4,11 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // CreateAppSession creates a new application session between participants. @@ -35,6 +37,11 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { return } + if reqPayload.Definition.Application == "" { + c.Fail(nil, "application id is required") + return + } + appDef, err := unmapAppDefinitionV1(reqPayload.Definition) if err != nil { c.Fail(err, "invalid app definition") @@ -101,25 +108,71 @@ func (h *Handler) CreateAppSession(c *rpc.Context) { } err = h.useStoreInTx(func(tx Store) error { + registeredApp, err := tx.GetApp(appDef.ApplicationID) + if err != nil { + return rpc.Errorf("failed to look up application: %v", err) + } + + // App must be registered regardless of CreationApprovalNotRequired flag. + if registeredApp == nil { + return rpc.Errorf("application %s is not registered", appDef.ApplicationID) + } + + if !registeredApp.App.CreationApprovalNotRequired { + if reqPayload.OwnerSig == "" { + return rpc.Errorf("owner_sig is required for this application") + } + + sigBytes, err := hexutil.Decode(reqPayload.OwnerSig) + if err != nil { + return rpc.Errorf("failed to decode signature: %v", err) + } + if len(sigBytes) == 0 { + return rpc.Errorf("empty owner_sig after decode") + } + + sigType := app.AppSessionSignerTypeV1(sigBytes[0]) + appSessionSignerValidator := app.NewAppSessionKeySigValidatorV1( + func(sessionKeyAddr string) (string, error) { + return tx.GetAppSessionKeyOwner(sessionKeyAddr, appSessionID) + }, + ) + recoveredOwnerWallet, err := appSessionSignerValidator.Recover(packedRequest, sigBytes) + if err != nil { + h.metrics.IncAppSessionUpdateSigValidation(appSessionID, sigType, false) + return rpc.Errorf("failed to recover user wallet: %v", err) + } + h.metrics.IncAppSessionUpdateSigValidation(appSessionID, sigType, true) + + if !strings.EqualFold(recoveredOwnerWallet, registeredApp.App.OwnerWallet) { + return rpc.Errorf("invalid owner signature: signer %s is not the app owner", recoveredOwnerWallet) + } + } + + err = h.actionGateway.AllowAction(tx, registeredApp.App.OwnerWallet, core.GatedActionAppSessionCreation) + if err != nil { + return rpc.NewError(err) + } + // Create app session with 0 allocations appSession := app.AppSessionV1{ - SessionID: appSessionID, - Application: appDef.Application, - Participants: appDef.Participants, - Quorum: appDef.Quorum, - Nonce: appDef.Nonce, - Status: app.AppSessionStatusOpen, - Version: 1, - SessionData: reqPayload.SessionData, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + SessionID: appSessionID, + ApplicationID: appDef.ApplicationID, + Participants: appDef.Participants, + Quorum: appDef.Quorum, + Nonce: appDef.Nonce, + Status: app.AppSessionStatusOpen, + Version: 1, + SessionData: reqPayload.SessionData, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } if err := tx.CreateAppSession(appSession); err != nil { return rpc.Errorf("failed to create app session: %v", err) } - if err := h.verifyQuorum(tx, appSessionID, appDef.Application, participantWeights, appDef.Quorum, packedRequest, reqPayload.QuorumSigs); err != nil { + if err := h.verifyQuorum(tx, appSessionID, appDef.ApplicationID, participantWeights, appDef.Quorum, packedRequest, reqPayload.QuorumSigs); err != nil { return err } diff --git a/clearnode/api/app_session_v1/create_app_session_test.go b/clearnode/api/app_session_v1/create_app_session_test.go index 552644b47..ccbab0721 100644 --- a/clearnode/api/app_session_v1/create_app_session_test.go +++ b/clearnode/api/app_session_v1/create_app_session_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestCreateAppSession_Success(t *testing.T) { @@ -29,6 +29,7 @@ func TestCreateAppSession_Success(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -44,7 +45,7 @@ func TestCreateAppSession_Success(t *testing.T) { // Build the app.AppDefinitionV1 for signing appDef := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 1}, {WalletAddress: participant2, SignatureWeight: 1}, @@ -76,6 +77,9 @@ func TestCreateAppSession_Success(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.MatchedBy(func(session any) bool { return true // Accept any app session for now })).Return(nil).Once() @@ -129,6 +133,7 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -146,7 +151,7 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { // Build the app.AppDefinitionV1 for signing appDef := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 2}, {WalletAddress: participant2, SignatureWeight: 1}, @@ -186,6 +191,9 @@ func TestCreateAppSession_QuorumWithMultipleSignatures(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -227,6 +235,7 @@ func TestCreateAppSession_ZeroNonce(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -290,6 +299,7 @@ func TestCreateAppSession_QuorumExceedsTotalWeights(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -359,6 +369,7 @@ func TestCreateAppSession_NoSignatures(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -422,6 +433,7 @@ func TestCreateAppSession_SignatureFromNonParticipant(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -436,7 +448,7 @@ func TestCreateAppSession_SignatureFromNonParticipant(t *testing.T) { // Build the app.AppDefinitionV1 for signing (with participant1 only) appDef := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 1}, }, @@ -461,6 +473,9 @@ func TestCreateAppSession_SignatureFromNonParticipant(t *testing.T) { QuorumSigs: []string{sig}, } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -500,6 +515,7 @@ func TestCreateAppSession_QuorumNotMet(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -516,7 +532,7 @@ func TestCreateAppSession_QuorumNotMet(t *testing.T) { // Build the app.AppDefinitionV1 for signing appDef := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 1}, {WalletAddress: participant2, SignatureWeight: 1}, @@ -553,6 +569,9 @@ func TestCreateAppSession_QuorumNotMet(t *testing.T) { }, } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -592,6 +611,7 @@ func TestCreateAppSession_DuplicateSignatures(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -607,7 +627,7 @@ func TestCreateAppSession_DuplicateSignatures(t *testing.T) { // Build the app.AppDefinitionV1 for signing appDef := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 1}, {WalletAddress: participant2, SignatureWeight: 1}, @@ -641,6 +661,9 @@ func TestCreateAppSession_DuplicateSignatures(t *testing.T) { }, } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -681,6 +704,7 @@ func TestCreateAppSession_InvalidSignatureHex(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -707,6 +731,9 @@ func TestCreateAppSession_InvalidSignatureHex(t *testing.T) { QuorumSigs: []string{"not-valid-hex"}, // Invalid hex string } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -746,6 +773,7 @@ func TestCreateAppSession_SignatureRecoveryFailure(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -773,6 +801,9 @@ func TestCreateAppSession_SignatureRecoveryFailure(t *testing.T) { QuorumSigs: []string{"0xa100000000"}, } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", CreationApprovalNotRequired: true}, + }, nil).Once() mockStore.On("CreateAppSession", mock.Anything).Return(nil).Once() // Create RPC context @@ -796,3 +827,246 @@ func TestCreateAppSession_SignatureRecoveryFailure(t *testing.T) { // Verify mocks were called mockStore.AssertExpectations(t) } + +func TestCreateAppSession_AppNotRegistered(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, + ) + + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + appDef := app.AppDefinitionV1{ + ApplicationID: "unregistered-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + } + sig1 := wallet1.SignCreateRequest(t, appDef, "") + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "unregistered-app", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: "12345", + }, + QuorumSigs: []string{sig1}, + } + + // GetApp returns nil (not found) + mockStore.On("GetApp", "unregistered-app").Return(nil, nil).Once() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), + } + + handler.CreateAppSession(ctx) + + assert.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "not registered") + + mockStore.AssertExpectations(t) +} + +func TestCreateAppSession_OwnerSigRequired(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, + ) + + wallet1 := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + appDef := app.AppDefinitionV1{ + ApplicationID: "restricted-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + } + sig1 := wallet1.SignCreateRequest(t, appDef, "") + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "restricted-app", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: "12345", + }, + QuorumSigs: []string{sig1}, + // No OwnerSig provided + } + + // App requires approval (CreationApprovalNotRequired = false) + mockStore.On("GetApp", "restricted-app").Return(&app.AppInfoV1{ + App: app.AppV1{ + ID: "restricted-app", + OwnerWallet: "0xowneraddr", + CreationApprovalNotRequired: false, + }, + }, nil).Once() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), + } + + handler.CreateAppSession(ctx) + + assert.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "owner_sig is required") + + mockStore.AssertExpectations(t) +} + +func TestCreateAppSession_OwnerSigSuccess(t *testing.T) { + mockStore := new(MockStore) + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + mockSigner := NewMockSigner() + mockAssetStore := new(MockAssetStore) + mockStatePacker := new(MockStatePacker) + + handler := NewHandler( + storeTxProvider, + mockAssetStore, + &MockActionGateway{}, + mockSigner, + core.NewStateAdvancerV1(mockAssetStore), + mockStatePacker, + "0xnode", + metrics.NewNoopRuntimeMetricExporter(), + 32, 1024, 256, 16, + ) + + // Create participant and owner wallets + wallet1 := NewTestAppSessionWallet(t) + ownerWallet := NewTestAppSessionWallet(t) + participant1 := wallet1.Address + + appDef := app.AppDefinitionV1{ + ApplicationID: "restricted-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: 12345, + } + sessionData := `{"game": "poker"}` + + // Participant signs for quorum + sig1 := wallet1.SignCreateRequest(t, appDef, sessionData) + // Owner signs for approval + ownerSig := ownerWallet.SignCreateRequest(t, appDef, sessionData) + + reqPayload := rpc.AppSessionsV1CreateAppSessionRequest{ + Definition: rpc.AppDefinitionV1{ + Application: "restricted-app", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Nonce: "12345", + }, + QuorumSigs: []string{sig1}, + SessionData: sessionData, + OwnerSig: ownerSig, + } + + // App requires approval — owner wallet matches + mockStore.On("GetApp", "restricted-app").Return(&app.AppInfoV1{ + App: app.AppV1{ + ID: "restricted-app", + OwnerWallet: ownerWallet.Address, + CreationApprovalNotRequired: false, + }, + }, nil).Once() + mockStore.On("CreateAppSession", mock.MatchedBy(func(session any) bool { + return true + })).Return(nil).Once() + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppSessionsV1CreateAppSessionMethod), payload), + } + + handler.CreateAppSession(ctx) + + assert.NotNil(t, ctx.Response) + + if respErr := ctx.Response.Error(); respErr != nil { + t.Fatalf("Unexpected error response: %v", respErr) + } + + assert.Equal(t, rpc.MsgTypeResp, ctx.Response.Type) + + var resp rpc.AppSessionsV1CreateAppSessionResponse + err = ctx.Response.Payload.Translate(&resp) + require.NoError(t, err) + + assert.NotEmpty(t, resp.AppSessionID) + assert.Equal(t, "1", resp.Version) + assert.Equal(t, app.AppSessionStatusOpen.String(), resp.Status) + + mockStore.AssertExpectations(t) +} diff --git a/clearnode/api/app_session_v1/get_app_definition.go b/clearnode/api/app_session_v1/get_app_definition.go index a9706186f..849b21a05 100644 --- a/clearnode/api/app_session_v1/get_app_definition.go +++ b/clearnode/api/app_session_v1/get_app_definition.go @@ -3,7 +3,7 @@ package app_session_v1 import ( "strconv" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetAppDefinition retrieves the application definition for a specific app session. @@ -36,7 +36,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) { } definition = rpc.AppDefinitionV1{ - Application: session.Application, + Application: session.ApplicationID, Participants: participants, Quorum: session.Quorum, Nonce: strconv.FormatUint(session.Nonce, 10), diff --git a/clearnode/api/app_session_v1/get_app_definition_test.go b/clearnode/api/app_session_v1/get_app_definition_test.go index a07105b14..8f3fb7231 100644 --- a/clearnode/api/app_session_v1/get_app_definition_test.go +++ b/clearnode/api/app_session_v1/get_app_definition_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetAppDefinition_Success(t *testing.T) { @@ -39,8 +39,8 @@ func TestGetAppDefinition_Success(t *testing.T) { participant2 := "0x9876543210987654321098765432109876543210" session := &app.AppSessionV1{ - SessionID: sessionID, - Application: "game", + SessionID: sessionID, + ApplicationID: "game", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 1}, {WalletAddress: participant2, SignatureWeight: 1}, diff --git a/clearnode/api/app_session_v1/get_app_sessions.go b/clearnode/api/app_session_v1/get_app_sessions.go index bdb953128..2c5d1c196 100644 --- a/clearnode/api/app_session_v1/get_app_sessions.go +++ b/clearnode/api/app_session_v1/get_app_sessions.go @@ -3,9 +3,9 @@ package app_session_v1 import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetAppSessions retrieves application sessions with optional filtering. diff --git a/clearnode/api/app_session_v1/get_app_sessions_test.go b/clearnode/api/app_session_v1/get_app_sessions_test.go index b953d415b..1367c81b8 100644 --- a/clearnode/api/app_session_v1/get_app_sessions_test.go +++ b/clearnode/api/app_session_v1/get_app_sessions_test.go @@ -10,10 +10,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { @@ -41,8 +41,8 @@ func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { sessions := []app.AppSessionV1{ { - SessionID: "session1", - Application: "game", + SessionID: "session1", + ApplicationID: "game", Participants: []app.AppParticipantV1{ {WalletAddress: participant, SignatureWeight: 1}, {WalletAddress: participant2, SignatureWeight: 1}, @@ -56,8 +56,8 @@ func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { UpdatedAt: time.Now(), }, { - SessionID: "session2", - Application: "betting", + SessionID: "session2", + ApplicationID: "betting", Participants: []app.AppParticipantV1{ {WalletAddress: participant, SignatureWeight: 1}, }, @@ -116,17 +116,17 @@ func TestGetAppSessions_SuccessWithParticipant(t *testing.T) { // Verify first session assert.Equal(t, "session1", response.AppSessions[0].AppSessionID) assert.Equal(t, "closed", response.AppSessions[0].Status) - assert.Len(t, response.AppSessions[0].Participants, 2) - assert.Equal(t, participant, response.AppSessions[0].Participants[0].WalletAddress) - assert.Equal(t, uint8(2), response.AppSessions[0].Quorum) + assert.Len(t, response.AppSessions[0].AppDefinitionV1.Participants, 2) + assert.Equal(t, participant, response.AppSessions[0].AppDefinitionV1.Participants[0].WalletAddress) + assert.Equal(t, uint8(2), response.AppSessions[0].AppDefinitionV1.Quorum) assert.Equal(t, "1", response.AppSessions[0].Version) assert.NotNil(t, response.AppSessions[0].SessionData) // Verify second session assert.Equal(t, "session2", response.AppSessions[1].AppSessionID) assert.Equal(t, "open", response.AppSessions[1].Status) - assert.Len(t, response.AppSessions[1].Participants, 1) - assert.Equal(t, uint8(1), response.AppSessions[1].Quorum) + assert.Len(t, response.AppSessions[1].AppDefinitionV1.Participants, 1) + assert.Equal(t, uint8(1), response.AppSessions[1].AppDefinitionV1.Quorum) assert.Equal(t, "5", response.AppSessions[1].Version) assert.Nil(t, response.AppSessions[1].SessionData) @@ -165,8 +165,8 @@ func TestGetAppSessions_SuccessWithAppSessionID(t *testing.T) { sessions := []app.AppSessionV1{ { - SessionID: sessionID, - Application: "game", + SessionID: sessionID, + ApplicationID: "game", Participants: []app.AppParticipantV1{ {WalletAddress: participant, SignatureWeight: 1}, }, @@ -296,8 +296,8 @@ func TestGetAppSessions_WithStatusFilter(t *testing.T) { sessions := []app.AppSessionV1{ { - SessionID: "session1", - Application: "game", + SessionID: "session1", + ApplicationID: "game", Participants: []app.AppParticipantV1{ {WalletAddress: participant, SignatureWeight: 1}, }, diff --git a/clearnode/api/app_session_v1/get_last_key_states.go b/clearnode/api/app_session_v1/get_last_key_states.go index e741b9deb..83976a1c5 100644 --- a/clearnode/api/app_session_v1/get_last_key_states.go +++ b/clearnode/api/app_session_v1/get_last_key_states.go @@ -1,9 +1,9 @@ package app_session_v1 import ( - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetLastKeyStates retrieves the latest session key states for a user with optional filtering by session key. diff --git a/clearnode/api/app_session_v1/handler.go b/clearnode/api/app_session_v1/handler.go index 1f3c59c78..bfac5079b 100644 --- a/clearnode/api/app_session_v1/handler.go +++ b/clearnode/api/app_session_v1/handler.go @@ -7,18 +7,19 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" ) // Handler manages app session operations and provides RPC endpoints for app session management. type Handler struct { useStoreInTx StoreTxProvider assetStore AssetStore + actionGateway ActionGateway signer sign.Signer stateAdvancer core.StateAdvancer statePacker core.StatePacker @@ -34,6 +35,7 @@ type Handler struct { func NewHandler( useStoreInTx StoreTxProvider, assetStore AssetStore, + actionGateway ActionGateway, signer sign.Signer, stateAdvancer core.StateAdvancer, statePacker core.StatePacker, @@ -44,6 +46,7 @@ func NewHandler( return &Handler{ useStoreInTx: useStoreInTx, assetStore: assetStore, + actionGateway: actionGateway, signer: signer, stateAdvancer: stateAdvancer, statePacker: statePacker, diff --git a/clearnode/api/app_session_v1/interface.go b/clearnode/api/app_session_v1/interface.go index 056a820b0..5c13464b9 100644 --- a/clearnode/api/app_session_v1/interface.go +++ b/clearnode/api/app_session_v1/interface.go @@ -1,13 +1,17 @@ package app_session_v1 import ( - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) // Store defines the persistence layer interface for app session management. type Store interface { + // App registry operations + GetApp(appID string) (*app.AppInfoV1, error) + // App session operations CreateAppSession(session app.AppSessionV1) error GetAppSession(sessionID string) (*app.AppSessionV1, error) @@ -42,6 +46,13 @@ type Store interface { // Channel Session key state operations ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) + + action_gateway.Store +} + +type ActionGateway interface { + // AllowAction checks if a user is allowed to perform a specific gated action based on their past activity and allowances. + AllowAction(tx action_gateway.Store, userAddress string, gatedAction core.GatedAction) error } // StoreTxHandler is a function that executes Store operations within a transaction. diff --git a/clearnode/api/app_session_v1/rebalance_app_sessions.go b/clearnode/api/app_session_v1/rebalance_app_sessions.go index 4cebc5b01..f066c6cf1 100644 --- a/clearnode/api/app_session_v1/rebalance_app_sessions.go +++ b/clearnode/api/app_session_v1/rebalance_app_sessions.go @@ -6,10 +6,10 @@ import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // RebalanceAppSessions processes multi-session rebalancing operations atomically. @@ -103,6 +103,18 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { if appSession == nil { return rpc.Errorf("app session not found: %s", update.AppStateUpdate.AppSessionID) } + registeredApp, err := tx.GetApp(appSession.ApplicationID) + if err != nil { + return rpc.Errorf("failed to look up application: %v", err) + } + if registeredApp == nil { + return rpc.Errorf("application %s is not registered", appSession.ApplicationID) + } + + err = h.actionGateway.AllowAction(tx, registeredApp.App.OwnerWallet, update.AppStateUpdate.Intent.GatedAction()) + if err != nil { + return rpc.NewError(err) + } if len(update.QuorumSigs) > len(appSession.Participants) { return rpc.Errorf("quorum_sigs count (%d) exceeds participants count (%d)", len(update.QuorumSigs), len(appSession.Participants)) } @@ -125,7 +137,7 @@ func (h *Handler) RebalanceAppSessions(c *rpc.Context) { return rpc.Errorf("failed to pack app state update for session %s: %v", update.AppStateUpdate.AppSessionID, err) } - if err := h.verifyQuorum(tx, update.AppStateUpdate.AppSessionID, appSession.Application, participantWeights, appSession.Quorum, packedStateUpdate, update.QuorumSigs); err != nil { + if err := h.verifyQuorum(tx, update.AppStateUpdate.AppSessionID, appSession.ApplicationID, participantWeights, appSession.Quorum, packedStateUpdate, update.QuorumSigs); err != nil { return rpc.Errorf("quorum verification failed for session %s: %v", update.AppStateUpdate.AppSessionID, err) } diff --git a/clearnode/api/app_session_v1/rebalance_app_sessions_test.go b/clearnode/api/app_session_v1/rebalance_app_sessions_test.go index 5305b521e..aef9cb2e9 100644 --- a/clearnode/api/app_session_v1/rebalance_app_sessions_test.go +++ b/clearnode/api/app_session_v1/rebalance_app_sessions_test.go @@ -9,10 +9,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // assertSuccess checks if the RPC context has a successful response @@ -43,6 +43,7 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -59,8 +60,8 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" session1 := &app.AppSessionV1{ - SessionID: sessionID1, - Application: "test-app", + SessionID: sessionID1, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet1.Address, SignatureWeight: 10}, }, @@ -71,8 +72,8 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { } session2 := &app.AppSessionV1{ - SessionID: sessionID2, - Application: "test-app", + SessionID: sessionID2, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet2.Address, SignatureWeight: 10}, }, @@ -149,6 +150,9 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { } // Mock expectations for session 1 + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID1).Return(session1, nil) mockStore.On("GetParticipantAllocations", sessionID1).Return(currentAllocations1, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { @@ -158,6 +162,9 @@ func TestRebalanceAppSessions_Success_TwoSessions(t *testing.T) { })).Return(nil).Once() // Mock expectations for session 2 + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID2).Return(session2, nil) mockStore.On("GetParticipantAllocations", sessionID2).Return(currentAllocations2, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(session app.AppSessionV1) bool { @@ -207,6 +214,7 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -223,8 +231,8 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" session1 := &app.AppSessionV1{ - SessionID: sessionID1, - Application: "test-app", + SessionID: sessionID1, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet1.Address, SignatureWeight: 10}, }, @@ -234,8 +242,8 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { } session2 := &app.AppSessionV1{ - SessionID: sessionID2, - Application: "test-app", + SessionID: sessionID2, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet2.Address, SignatureWeight: 10}, }, @@ -313,12 +321,18 @@ func TestRebalanceAppSessions_Success_MultiAsset(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID1).Return(session1, nil) mockStore.On("GetParticipantAllocations", sessionID1).Return(currentAllocations1, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(s app.AppSessionV1) bool { return s.SessionID == sessionID1 && s.Version == 2 })).Return(nil).Once() + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID2).Return(session2, nil) mockStore.On("GetParticipantAllocations", sessionID2).Return(currentAllocations2, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(s app.AppSessionV1) bool { @@ -360,6 +374,7 @@ func TestRebalanceAppSessions_Error_InsufficientSessions(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -417,6 +432,7 @@ func TestRebalanceAppSessions_Error_InvalidIntent(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -490,6 +506,7 @@ func TestRebalanceAppSessions_Error_DuplicateSession(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -565,6 +582,7 @@ func TestRebalanceAppSessions_Error_ConservationViolation(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -581,8 +599,8 @@ func TestRebalanceAppSessions_Error_ConservationViolation(t *testing.T) { sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" session1 := &app.AppSessionV1{ - SessionID: sessionID1, - Application: "test-app", + SessionID: sessionID1, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet1.Address, SignatureWeight: 10}, }, @@ -592,8 +610,8 @@ func TestRebalanceAppSessions_Error_ConservationViolation(t *testing.T) { } session2 := &app.AppSessionV1{ - SessionID: sessionID2, - Application: "test-app", + SessionID: sessionID2, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: wallet2.Address, SignatureWeight: 10}, }, @@ -664,12 +682,18 @@ func TestRebalanceAppSessions_Error_ConservationViolation(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID1).Return(session1, nil) mockStore.On("GetParticipantAllocations", sessionID1).Return(currentAllocations1, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(s app.AppSessionV1) bool { return s.SessionID == sessionID1 && s.Version == 2 })).Return(nil).Once() + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID2).Return(session2, nil) mockStore.On("GetParticipantAllocations", sessionID2).Return(currentAllocations2, nil) mockStore.On("UpdateAppSession", mock.MatchedBy(func(s app.AppSessionV1) bool { @@ -704,6 +728,7 @@ func TestRebalanceAppSessions_Error_SessionNotFound(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -784,6 +809,7 @@ func TestRebalanceAppSessions_Error_ClosedSession(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -799,9 +825,10 @@ func TestRebalanceAppSessions_Error_ClosedSession(t *testing.T) { sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" session1 := &app.AppSessionV1{ - SessionID: sessionID1, - Status: app.AppSessionStatusClosed, // Closed - Version: 1, + SessionID: sessionID1, + ApplicationID: "test-app", + Status: app.AppSessionStatusClosed, // Closed + Version: 1, Participants: []app.AppParticipantV1{ {WalletAddress: wallet1.Address, SignatureWeight: 1}, {WalletAddress: wallet2.Address, SignatureWeight: 1}, @@ -844,6 +871,9 @@ func TestRebalanceAppSessions_Error_ClosedSession(t *testing.T) { }, } + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID1).Return(session1, nil) payload, err := rpc.NewPayload(reqPayload) @@ -874,6 +904,7 @@ func TestRebalanceAppSessions_Error_InvalidVersion(t *testing.T) { handler := NewHandler( storeTxProvider, nil, + &MockActionGateway{}, nil, nil, nil, @@ -889,9 +920,10 @@ func TestRebalanceAppSessions_Error_InvalidVersion(t *testing.T) { sessionID2 := "0x2222222222222222222222222222222222222222222222222222222222222222" session1 := &app.AppSessionV1{ - SessionID: sessionID1, - Status: app.AppSessionStatusOpen, - Version: 5, // Current version is 5 + SessionID: sessionID1, + ApplicationID: "test-app", + Status: app.AppSessionStatusOpen, + Version: 5, // Current version is 5 Participants: []app.AppParticipantV1{ {WalletAddress: wallet1.Address, SignatureWeight: 1}, {WalletAddress: wallet2.Address, SignatureWeight: 1}, @@ -934,6 +966,9 @@ func TestRebalanceAppSessions_Error_InvalidVersion(t *testing.T) { }, } + mockStore.On("GetApp", mock.Anything).Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil) mockStore.On("GetAppSession", sessionID1).Return(session1, nil) payload, err := rpc.NewPayload(reqPayload) diff --git a/clearnode/api/app_session_v1/submit_app_state.go b/clearnode/api/app_session_v1/submit_app_state.go index d03707d01..e5c1290c5 100644 --- a/clearnode/api/app_session_v1/submit_app_state.go +++ b/clearnode/api/app_session_v1/submit_app_state.go @@ -4,10 +4,10 @@ import ( "context" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" ) @@ -65,6 +65,18 @@ func (h *Handler) SubmitAppState(c *rpc.Context) { return rpc.Errorf("app session is already closed") } + registeredApp, err := tx.GetApp(appSession.ApplicationID) + if err != nil { + return rpc.Errorf("failed to look up application: %v", err) + } + if registeredApp == nil { + return rpc.Errorf("application %s is not registered", appSession.ApplicationID) + } + err = h.actionGateway.AllowAction(tx, registeredApp.App.OwnerWallet, appStateUpd.Intent.GatedAction()) + if err != nil { + return rpc.NewError(err) + } + if len(reqPayload.QuorumSigs) > len(appSession.Participants) { return rpc.Errorf("quorum_sigs count (%d) exceeds participants count (%d)", len(reqPayload.QuorumSigs), len(appSession.Participants)) } @@ -84,7 +96,7 @@ func (h *Handler) SubmitAppState(c *rpc.Context) { return rpc.Errorf("failed to pack app state update: %v", err) } - if err := h.verifyQuorum(tx, appStateUpd.AppSessionID, appSession.Application, participantWeights, appSession.Quorum, packedStateUpdate, reqPayload.QuorumSigs); err != nil { + if err := h.verifyQuorum(tx, appStateUpd.AppSessionID, appSession.ApplicationID, participantWeights, appSession.Quorum, packedStateUpdate, reqPayload.QuorumSigs); err != nil { return err } diff --git a/clearnode/api/app_session_v1/submit_app_state_test.go b/clearnode/api/app_session_v1/submit_app_state_test.go index 0300f49d9..3638e600b 100644 --- a/clearnode/api/app_session_v1/submit_app_state_test.go +++ b/clearnode/api/app_session_v1/submit_app_state_test.go @@ -5,10 +5,10 @@ import ( "errors" "testing" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -29,6 +29,7 @@ func TestSubmitAppState_OperateIntent_NoRedistribution_Success(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -43,8 +44,8 @@ func TestSubmitAppState_OperateIntent_NoRedistribution_Success(t *testing.T) { participant2 := "0x2222222222222222222222222222222222222222" existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 5}, {WalletAddress: participant2, SignatureWeight: 5}, @@ -96,6 +97,9 @@ func TestSubmitAppState_OperateIntent_NoRedistribution_Success(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) @@ -140,6 +144,7 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -154,8 +159,8 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { participant2 := "0x2222222222222222222222222222222222222222" existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 5}, {WalletAddress: participant2, SignatureWeight: 5}, @@ -209,6 +214,9 @@ func TestSubmitAppState_OperateIntent_WithRedistribution_Success(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) @@ -257,6 +265,7 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -270,8 +279,8 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { participant1 := wallet1.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 10}, }, @@ -313,6 +322,9 @@ func TestSubmitAppState_WithdrawIntent_Success(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) @@ -367,6 +379,7 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -381,8 +394,8 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { participant2 := "0x2222222222222222222222222222222222222222" existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 5}, {WalletAddress: participant2, SignatureWeight: 5}, @@ -430,6 +443,9 @@ func TestSubmitAppState_CloseIntent_Success(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) @@ -491,6 +507,7 @@ func TestSubmitAppState_CloseIntent_AllocationMismatch_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -504,8 +521,8 @@ func TestSubmitAppState_CloseIntent_AllocationMismatch_Rejected(t *testing.T) { participant1 := wallet1.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 10}, }, @@ -547,6 +564,9 @@ func TestSubmitAppState_CloseIntent_AllocationMismatch_Rejected(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil).Maybe() @@ -586,6 +606,7 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -600,8 +621,8 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { participant2 := "0x2222222222222222222222222222222222222222" existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 5}, {WalletAddress: participant2, SignatureWeight: 5}, @@ -651,6 +672,9 @@ func TestSubmitAppState_OperateIntent_MissingAllocation_Rejected(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) @@ -695,6 +719,7 @@ func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -708,8 +733,8 @@ func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) participant1 := wallet1.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 10}, }, @@ -752,6 +777,9 @@ func TestSubmitAppState_WithdrawIntent_MissingAllocation_Rejected(t *testing.T) } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil).Maybe() @@ -802,6 +830,7 @@ func TestSubmitAppState_DepositIntent_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -856,6 +885,7 @@ func TestSubmitAppState_ClosedSession_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -884,6 +914,9 @@ func TestSubmitAppState_ClosedSession_Rejected(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) // Create RPC context @@ -922,6 +955,7 @@ func TestSubmitAppState_InvalidVersion_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -933,9 +967,10 @@ func TestSubmitAppState_InvalidVersion_Rejected(t *testing.T) { appSessionID := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Status: app.AppSessionStatusOpen, - Version: 5, // Current version is 5 + SessionID: appSessionID, + ApplicationID: "test-app", + Status: app.AppSessionStatusOpen, + Version: 5, // Current version is 5 Participants: []app.AppParticipantV1{ {WalletAddress: "0x1111111111111111111111111111111111111111", SignatureWeight: 1}, }, @@ -954,6 +989,9 @@ func TestSubmitAppState_InvalidVersion_Rejected(t *testing.T) { } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) // Create RPC context @@ -992,6 +1030,7 @@ func TestSubmitAppState_SessionNotFound_Rejected(t *testing.T) { handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -1051,6 +1090,7 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -1064,8 +1104,8 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin participant1 := wallet1.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 10}, }, @@ -1113,6 +1153,9 @@ func TestSubmitAppState_OperateIntent_InvalidDecimalPrecision_Rejected(t *testin } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) @@ -1155,6 +1198,7 @@ func TestSubmitAppState_WithdrawIntent_InvalidDecimalPrecision_Rejected(t *testi handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -1168,8 +1212,8 @@ func TestSubmitAppState_WithdrawIntent_InvalidDecimalPrecision_Rejected(t *testi participant1 := wallet1.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 10}, }, @@ -1213,6 +1257,9 @@ func TestSubmitAppState_WithdrawIntent_InvalidDecimalPrecision_Rejected(t *testi } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockAssetStore.On("GetAssetDecimals", "USDC").Return(uint8(6), nil) @@ -1255,6 +1302,7 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te handler := NewHandler( storeTxProvider, mockAssetStore, + &MockActionGateway{}, mockSigner, core.NewStateAdvancerV1(mockAssetStore), mockStatePacker, @@ -1270,8 +1318,8 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te participant2 := wallet2.Address existingSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ {WalletAddress: participant1, SignatureWeight: 50}, {WalletAddress: participant2, SignatureWeight: 50}, @@ -1322,6 +1370,9 @@ func TestSubmitAppState_OperateIntent_RedistributeToNewParticipant_Success(t *te } // Mock expectations + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingSession, nil) mockStore.On("GetParticipantAllocations", appSessionID).Return(currentAllocations, nil) mockStore.On("GetAppSessionBalances", appSessionID).Return(sessionBalances, nil) diff --git a/clearnode/api/app_session_v1/submit_deposit_state.go b/clearnode/api/app_session_v1/submit_deposit_state.go index 0097b5ff8..c273c608e 100644 --- a/clearnode/api/app_session_v1/submit_deposit_state.go +++ b/clearnode/api/app_session_v1/submit_deposit_state.go @@ -3,11 +3,11 @@ package app_session_v1 import ( "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" ) @@ -44,8 +44,48 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { var nodeSig string err = h.useStoreInTx(func(tx Store) error { + appSession, err := tx.GetAppSession(appStateUpd.AppSessionID) + if err != nil { + return rpc.Errorf("app session not found: %v", err) + } + if appSession == nil { + return rpc.Errorf("app session not found") + } + if len(reqPayload.QuorumSigs) > len(appSession.Participants) { + return rpc.Errorf("quorum_sigs count (%d) exceeds participants count (%d)", len(reqPayload.QuorumSigs), len(appSession.Participants)) + } + if appSession.Status == app.AppSessionStatusClosed { + return rpc.Errorf("app session is already closed") + } + if appStateUpd.Version != appSession.Version+1 { + return rpc.Errorf("invalid app session version: expected %d, got %d", appSession.Version+1, appStateUpd.Version) + } + + if appStateUpd.Intent != app.AppStateUpdateIntentDeposit { + return rpc.Errorf("invalid intent: expected 'deposit', got '%s'", appStateUpd.Intent) + } + + participantWeights := getParticipantWeights(appSession.Participants) + + if len(reqPayload.QuorumSigs) == 0 { + return rpc.Errorf("no signatures provided") + } + + registeredApp, err := tx.GetApp(appSession.ApplicationID) + if err != nil { + return rpc.Errorf("failed to look up application: %v", err) + } + if registeredApp == nil { + return rpc.Errorf("application %s is not registered", appSession.ApplicationID) + } + + err = h.actionGateway.AllowAction(tx, registeredApp.App.OwnerWallet, appStateUpd.Intent.GatedAction()) + if err != nil { + return rpc.NewError(err) + } + // Lock the user's state to prevent concurrent modifications - _, err := tx.LockUserState(userState.UserWallet, userState.Asset) + _, err = tx.LockUserState(userState.UserWallet, userState.Asset) if err != nil { return rpc.Errorf("failed to lock user state: %v", err) } @@ -116,40 +156,13 @@ func (h *Handler) SubmitDepositState(c *rpc.Context) { } h.metrics.IncChannelStateSigValidation(sigType, true) - appSession, err := tx.GetAppSession(appStateUpd.AppSessionID) - if err != nil { - return rpc.Errorf("app session not found: %v", err) - } - if appSession == nil { - return rpc.Errorf("app session not found") - } - if len(reqPayload.QuorumSigs) > len(appSession.Participants) { - return rpc.Errorf("quorum_sigs count (%d) exceeds participants count (%d)", len(reqPayload.QuorumSigs), len(appSession.Participants)) - } - if appSession.Status == app.AppSessionStatusClosed { - return rpc.Errorf("app session is already closed") - } - if appStateUpd.Version != appSession.Version+1 { - return rpc.Errorf("invalid app session version: expected %d, got %d", appSession.Version+1, appStateUpd.Version) - } - - if appStateUpd.Intent != app.AppStateUpdateIntentDeposit { - return rpc.Errorf("invalid intent: expected 'deposit', got '%s'", appStateUpd.Intent) - } - - participantWeights := getParticipantWeights(appSession.Participants) - - if len(reqPayload.QuorumSigs) == 0 { - return rpc.Errorf("no signatures provided") - } - // Pack the app state update for signature verification packedStateUpdate, err := app.PackAppStateUpdateV1(appStateUpd) if err != nil { return rpc.Errorf("failed to pack app state update: %v", err) } - if err := h.verifyQuorum(tx, appStateUpd.AppSessionID, appSession.Application, participantWeights, appSession.Quorum, packedStateUpdate, reqPayload.QuorumSigs); err != nil { + if err := h.verifyQuorum(tx, appStateUpd.AppSessionID, appSession.ApplicationID, participantWeights, appSession.Quorum, packedStateUpdate, reqPayload.QuorumSigs); err != nil { return err } diff --git a/clearnode/api/app_session_v1/submit_deposit_state_test.go b/clearnode/api/app_session_v1/submit_deposit_state_test.go index 52d53e080..539667ae9 100644 --- a/clearnode/api/app_session_v1/submit_deposit_state_test.go +++ b/clearnode/api/app_session_v1/submit_deposit_state_test.go @@ -13,10 +13,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestSubmitDepositState_Success(t *testing.T) { @@ -29,6 +29,7 @@ func TestSubmitDepositState_Success(t *testing.T) { handler := &Handler{ assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), statePacker: mockStatePacker, useStoreInTx: func(handler StoreTxHandler) error { @@ -56,8 +57,8 @@ func TestSubmitDepositState_Success(t *testing.T) { // Create existing app session existingAppSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ { WalletAddress: participant1, @@ -152,6 +153,9 @@ func TestSubmitDepositState_Success(t *testing.T) { mockStore.On("GetLastUserState", participant1, asset, false).Return(currentUserState, nil).Once() mockStore.On("EnsureNoOngoingStateTransitions", participant1, asset).Return(nil).Once() mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingAppSession, nil).Once() // Mock allocations check - empty initially @@ -234,6 +238,7 @@ func TestSubmitDepositState_InvalidTransitionType(t *testing.T) { handler := &Handler{ assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), statePacker: mockStatePacker, useStoreInTx: func(handler StoreTxHandler) error { @@ -319,8 +324,22 @@ func TestSubmitDepositState_InvalidTransitionType(t *testing.T) { }, } - // Mock expectations - LockUserState is called before transition type check - mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Once() + // Mock expectations + existingAppSession := &app.AppSessionV1{ + SessionID: appSessionID, + ApplicationID: "test-app", + Participants: []app.AppParticipantV1{ + {WalletAddress: participant1, SignatureWeight: 1}, + }, + Quorum: 1, + Status: app.AppSessionStatusOpen, + Version: 1, + } + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() + mockStore.On("GetAppSession", appSessionID).Return(existingAppSession, nil).Once() + mockStore.On("LockUserState", participant1, asset).Return(decimal.Zero, nil).Maybe() // Create RPC request rpcState := toRPCState(userState) @@ -363,6 +382,7 @@ func TestSubmitDepositState_QuorumNotMet(t *testing.T) { handler := &Handler{ assetStore: mockAssetStore, + actionGateway: &MockActionGateway{}, stateAdvancer: core.NewStateAdvancerV1(mockAssetStore), statePacker: mockStatePacker, useStoreInTx: func(handler StoreTxHandler) error { @@ -390,8 +410,8 @@ func TestSubmitDepositState_QuorumNotMet(t *testing.T) { // Create existing app session with higher quorum requirement existingAppSession := &app.AppSessionV1{ - SessionID: appSessionID, - Application: "test-app", + SessionID: appSessionID, + ApplicationID: "test-app", Participants: []app.AppParticipantV1{ { WalletAddress: participant1, @@ -477,6 +497,9 @@ func TestSubmitDepositState_QuorumNotMet(t *testing.T) { mockStore.On("GetLastUserState", participant1, asset, false).Return(currentUserState, nil).Once() mockStore.On("EnsureNoOngoingStateTransitions", participant1, asset).Return(nil).Once() mockAssetStore.On("GetAssetDecimals", asset).Return(uint8(6), nil) + mockStore.On("GetApp", "test-app").Return(&app.AppInfoV1{ + App: app.AppV1{ID: "test-app", OwnerWallet: "0x0000000000000000000000000000000000000001"}, + }, nil).Maybe() mockStore.On("GetAppSession", appSessionID).Return(existingAppSession, nil).Once() // Create RPC request diff --git a/clearnode/api/app_session_v1/submit_session_key_state.go b/clearnode/api/app_session_v1/submit_session_key_state.go index 18dd1c313..c92970a93 100644 --- a/clearnode/api/app_session_v1/submit_session_key_state.go +++ b/clearnode/api/app_session_v1/submit_session_key_state.go @@ -7,10 +7,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" ) // SubmitSessionKeyState processes session key state submissions for registration and updates. diff --git a/clearnode/api/app_session_v1/testing.go b/clearnode/api/app_session_v1/testing.go index dcae3b3ff..a1a220f3f 100644 --- a/clearnode/api/app_session_v1/testing.go +++ b/clearnode/api/app_session_v1/testing.go @@ -3,6 +3,7 @@ package app_session_v1 import ( "strings" "testing" + "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -10,9 +11,10 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" ) // MockStore is a mock implementation of the Store interface @@ -128,11 +130,55 @@ func (m *MockStore) GetAppSessionKeyOwner(sessionKey, appSessionId string) (stri return args.String(0), args.Error(1) } +func (m *MockStore) GetApp(appID string) (*app.AppInfoV1, error) { + args := m.Called(appID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*app.AppInfoV1), args.Error(1) +} + func (m *MockStore) ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) { args := m.Called(wallet, sessionKey, asset, metadataHash) return args.Bool(0), args.Error(1) } +func (m *MockStore) GetAppCount(ownerWallet string) (uint64, error) { + args := m.Called(ownerWallet) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockStore) GetTotalUserStaked(wallet string) (decimal.Decimal, error) { + args := m.Called(wallet) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +func (m *MockStore) RecordAction(wallet string, gatedAction core.GatedAction) error { + args := m.Called(wallet, gatedAction) + return args.Error(0) +} + +func (m *MockStore) GetUserActionCount(wallet string, gatedAction core.GatedAction, window time.Duration) (uint64, error) { + args := m.Called(wallet, gatedAction, window) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockStore) GetUserActionCounts(userWallet string, window time.Duration) (map[core.GatedAction]uint64, error) { + args := m.Called(userWallet, window) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(map[core.GatedAction]uint64), args.Error(1) +} + +type MockActionGateway struct { + Err error +} + +func (m *MockActionGateway) AllowAction(_ action_gateway.Store, _ string, _ core.GatedAction) error { + return m.Err +} + // MockSigValidator is a mock implementation of the SigValidator interface type MockSigValidator struct { mock.Mock diff --git a/clearnode/api/app_session_v1/utils.go b/clearnode/api/app_session_v1/utils.go index 98d5f7df0..4381b2d62 100644 --- a/clearnode/api/app_session_v1/utils.go +++ b/clearnode/api/app_session_v1/utils.go @@ -7,9 +7,9 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" ) @@ -28,10 +28,10 @@ func unmapAppDefinitionV1(def rpc.AppDefinitionV1) (app.AppDefinitionV1, error) } return app.AppDefinitionV1{ - Application: def.Application, - Participants: participants, - Quorum: def.Quorum, - Nonce: nonce, + ApplicationID: def.Application, + Participants: participants, + Quorum: def.Quorum, + Nonce: nonce, }, nil } @@ -219,12 +219,15 @@ func mapAppSessionInfoV1(session app.AppSessionV1, allocations map[string]map[st return rpc.AppSessionInfoV1{ AppSessionID: session.SessionID, Status: session.Status.String(), - Participants: participants, - SessionData: sessionData, - Quorum: session.Quorum, - Version: strconv.FormatUint(session.Version, 10), - Nonce: strconv.FormatUint(session.Nonce, 10), - Allocations: rpcAllocations, + AppDefinitionV1: rpc.AppDefinitionV1{ + Application: session.ApplicationID, + Participants: participants, + Quorum: session.Quorum, + Nonce: strconv.FormatUint(session.Nonce, 10), + }, + SessionData: sessionData, + Version: strconv.FormatUint(session.Version, 10), + Allocations: rpcAllocations, } } diff --git a/clearnode/api/apps_v1/get_apps.go b/clearnode/api/apps_v1/get_apps.go new file mode 100644 index 000000000..3ab03b4e5 --- /dev/null +++ b/clearnode/api/apps_v1/get_apps.go @@ -0,0 +1,71 @@ +package apps_v1 + +import ( + "strconv" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +// GetApps retrieves registered applications with optional filtering. +func (h *Handler) GetApps(c *rpc.Context) { + var req rpc.AppsV1GetAppsRequest + if err := c.Request.Payload.Translate(&req); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + var paginationParams core.PaginationParams + if req.Pagination != nil { + paginationParams.Offset = req.Pagination.Offset + paginationParams.Limit = req.Pagination.Limit + paginationParams.Sort = req.Pagination.Sort + } + + apps, metadata, err := h.store.GetApps(req.AppID, req.OwnerWallet, &paginationParams) + if err != nil { + c.Fail(err, "failed to retrieve apps") + return + } + + response := rpc.AppsV1GetAppsResponse{ + Apps: make([]rpc.AppInfoV1, len(apps)), + Metadata: mapPaginationMetadataV1(metadata), + } + + for i, a := range apps { + response.Apps[i] = mapAppInfoV1(a) + } + + payload, err := rpc.NewPayload(response) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) +} + +func mapAppInfoV1(info app.AppInfoV1) rpc.AppInfoV1 { + return rpc.AppInfoV1{ + AppV1: rpc.AppV1{ + ID: info.App.ID, + OwnerWallet: info.App.OwnerWallet, + Metadata: info.App.Metadata, + Version: strconv.FormatUint(info.App.Version, 10), + CreationApprovalNotRequired: info.App.CreationApprovalNotRequired, + }, + CreatedAt: strconv.FormatInt(info.CreatedAt.Unix(), 10), + UpdatedAt: strconv.FormatInt(info.UpdatedAt.Unix(), 10), + } +} + +func mapPaginationMetadataV1(meta core.PaginationMetadata) rpc.PaginationMetadataV1 { + return rpc.PaginationMetadataV1{ + Page: meta.Page, + PerPage: meta.PerPage, + TotalCount: meta.TotalCount, + PageCount: meta.PageCount, + } +} diff --git a/clearnode/api/apps_v1/get_apps_test.go b/clearnode/api/apps_v1/get_apps_test.go new file mode 100644 index 000000000..aaeb8ed52 --- /dev/null +++ b/clearnode/api/apps_v1/get_apps_test.go @@ -0,0 +1,143 @@ +package apps_v1 + +import ( + "context" + "testing" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetApps_Success(t *testing.T) { + mockStore := &MockStore{ + getAppsFn: func(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + return []app.AppInfoV1{ + {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111", Metadata: "0x00", Version: 1}}, + {App: app.AppV1{ID: "app-2", OwnerWallet: "0x2222", Metadata: "0x00", Version: 1}}, + }, core.PaginationMetadata{TotalCount: 2, Page: 1, PerPage: 50}, nil + }, + } + + handler := NewHandler(mockStore, nil, nil, 4096) + + reqPayload := rpc.AppsV1GetAppsRequest{} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1GetAppsMethod), payload), + } + + handler.GetApps(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var resp rpc.AppsV1GetAppsResponse + require.NoError(t, ctx.Response.Payload.Translate(&resp)) + assert.Len(t, resp.Apps, 2) + assert.Equal(t, "app-1", resp.Apps[0].ID) + assert.Equal(t, "app-2", resp.Apps[1].ID) + assert.Equal(t, uint32(2), resp.Metadata.TotalCount) +} + +func TestGetApps_EmptyResults(t *testing.T) { + mockStore := &MockStore{ + getAppsFn: func(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + return []app.AppInfoV1{}, core.PaginationMetadata{TotalCount: 0}, nil + }, + } + + handler := NewHandler(mockStore, nil, nil, 4096) + + reqPayload := rpc.AppsV1GetAppsRequest{} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1GetAppsMethod), payload), + } + + handler.GetApps(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var resp rpc.AppsV1GetAppsResponse + require.NoError(t, ctx.Response.Payload.Translate(&resp)) + assert.Empty(t, resp.Apps) + assert.Equal(t, uint32(0), resp.Metadata.TotalCount) +} + +func TestGetApps_FilterByAppID(t *testing.T) { + mockStore := &MockStore{ + getAppsFn: func(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + require.NotNil(t, appID) + assert.Equal(t, "app-1", *appID) + return []app.AppInfoV1{ + {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111", Version: 1}}, + }, core.PaginationMetadata{TotalCount: 1, Page: 1, PerPage: 50}, nil + }, + } + + handler := NewHandler(mockStore, nil, nil, 4096) + + aid := "app-1" + reqPayload := rpc.AppsV1GetAppsRequest{AppID: &aid} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1GetAppsMethod), payload), + } + + handler.GetApps(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var resp rpc.AppsV1GetAppsResponse + require.NoError(t, ctx.Response.Payload.Translate(&resp)) + assert.Len(t, resp.Apps, 1) + assert.Equal(t, "app-1", resp.Apps[0].ID) +} + +func TestGetApps_FilterByOwnerWallet(t *testing.T) { + mockStore := &MockStore{ + getAppsFn: func(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + require.NotNil(t, ownerWallet) + assert.Equal(t, "0x1111", *ownerWallet) + return []app.AppInfoV1{ + {App: app.AppV1{ID: "app-1", OwnerWallet: "0x1111", Version: 1}}, + }, core.PaginationMetadata{TotalCount: 1, Page: 1, PerPage: 50}, nil + }, + } + + handler := NewHandler(mockStore, nil, nil, 4096) + + owner := "0x1111" + reqPayload := rpc.AppsV1GetAppsRequest{OwnerWallet: &owner} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1GetAppsMethod), payload), + } + + handler.GetApps(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var resp rpc.AppsV1GetAppsResponse + require.NoError(t, ctx.Response.Payload.Translate(&resp)) + assert.Len(t, resp.Apps, 1) + assert.Equal(t, "0x1111", resp.Apps[0].OwnerWallet) +} diff --git a/clearnode/api/apps_v1/handler.go b/clearnode/api/apps_v1/handler.go new file mode 100644 index 000000000..280e6e453 --- /dev/null +++ b/clearnode/api/apps_v1/handler.go @@ -0,0 +1,20 @@ +package apps_v1 + +// Handler manages app registry operations and provides RPC endpoints. +type Handler struct { + store Store + useStoreInTx StoreTxProvider + actionGateway ActionGateway + + maxAppMetadataLen int +} + +// NewHandler creates a new Handler instance with the provided dependencies. +func NewHandler(store Store, useStoreInTx StoreTxProvider, actionGateway ActionGateway, maxAppMetadataLen int) *Handler { + return &Handler{ + store: store, + useStoreInTx: useStoreInTx, + actionGateway: actionGateway, + maxAppMetadataLen: maxAppMetadataLen, + } +} diff --git a/clearnode/api/apps_v1/interface.go b/clearnode/api/apps_v1/interface.go new file mode 100644 index 000000000..06c9d94c2 --- /dev/null +++ b/clearnode/api/apps_v1/interface.go @@ -0,0 +1,33 @@ +package apps_v1 + +import ( + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" +) + +// StoreTxHandler is a function that executes Store operations within a transaction. +// If the handler returns an error, the transaction is rolled back; otherwise it's committed. +type StoreTxHandler func(Store) error + +// StoreTxProvider wraps Store operations in a database transaction. +// It accepts a StoreTxHandler and manages transaction lifecycle (begin, commit, rollback). +// Returns an error if the handler fails or the transaction cannot be committed. +type StoreTxProvider func(StoreTxHandler) error + +// Store defines the persistence layer interface for user data management. +// All methods should be implemented to work within database transactions. +type Store interface { + // CreateApp registers a new application. Returns an error if the app ID already exists. + CreateApp(entry app.AppV1) error + + // GetApps retrieves applications with optional filtering. + GetApps(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) + + action_gateway.Store +} + +type ActionGateway interface { + // AllowAppRegistration checks if a user is allowed to register a new application based on their past activity and allowances. + AllowAppRegistration(tx action_gateway.Store, userAddress string) error +} diff --git a/clearnode/api/apps_v1/submit_app_version.go b/clearnode/api/apps_v1/submit_app_version.go new file mode 100644 index 000000000..c604e76fe --- /dev/null +++ b/clearnode/api/apps_v1/submit_app_version.go @@ -0,0 +1,103 @@ +package apps_v1 + +import ( + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// SubmitAppVersion updates an entry in the app registry. +func (h *Handler) SubmitAppVersion(c *rpc.Context) { + var req rpc.AppsV1SubmitAppVersionRequest + if err := c.Request.Payload.Translate(&req); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + if !app.AppIDV1Regex.MatchString(req.App.ID) { + c.Fail(rpc.Errorf("invalid app ID: should match regex %s", app.AppIDV1Regex.String()), "") + return + } + if req.App.OwnerWallet == "" { + c.Fail(nil, "owner_wallet is required") + return + } + if req.OwnerSig == "" { + c.Fail(nil, "owner_sig is required") + return + } + if len(req.App.Metadata) > h.maxAppMetadataLen { + c.Fail(rpc.Errorf("metadata exceeds maximum length of %d characters", h.maxAppMetadataLen), "") + return + } + + version, err := strconv.ParseUint(req.App.Version, 10, 64) + if err != nil { + c.Fail(err, "invalid version") + return + } + + // Only creation (version 1) is supported for now + if version != 1 { + c.Fail(nil, "only version 1 (creation) is currently supported") + return + } + + err = h.useStoreInTx(func(tx Store) error { + err := h.actionGateway.AllowAppRegistration(tx, req.App.OwnerWallet) + if err != nil { + return rpc.NewError(err) + } + + appEntry := app.AppV1{ + ID: strings.ToLower(req.App.ID), + OwnerWallet: strings.ToLower(req.App.OwnerWallet), + Metadata: req.App.Metadata, + Version: version, + CreationApprovalNotRequired: req.App.CreationApprovalNotRequired, + } + + packedApp, err := app.PackAppV1(appEntry) + if err != nil { + return rpc.Errorf("failed to pack app data: %v", err) + } + + sigBytes, err := hexutil.Decode(req.OwnerSig) + if err != nil { + return rpc.Errorf("failed to decode owner signature: %v", err) + } + + sigValidator, err := sign.NewSigValidator(sign.TypeEthereumMsg) + if err != nil { + return rpc.Errorf("failed to create signature validator: %v", err) + } + + if err := sigValidator.Verify(appEntry.OwnerWallet, packedApp, sigBytes); err != nil { + return rpc.Errorf("invalid owner signature: %v", err) + } + + if err := tx.CreateApp(appEntry); err != nil { + return rpc.Errorf("failed to create app") + } + + return nil + }) + if err != nil { + c.Fail(err, "failed to create app") + return + } + + resp := rpc.AppsV1SubmitAppVersionResponse{} + payload, err := rpc.NewPayload(resp) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) +} diff --git a/clearnode/api/apps_v1/submit_app_version_test.go b/clearnode/api/apps_v1/submit_app_version_test.go new file mode 100644 index 000000000..5486fec86 --- /dev/null +++ b/clearnode/api/apps_v1/submit_app_version_test.go @@ -0,0 +1,242 @@ +package apps_v1 + +import ( + "context" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// testOwnerWallet generates a real ECDSA key pair, signs the packed app data, and returns +// the wallet address (lowercase hex), the hex-encoded signature, and the signer. +func testOwnerWallet(t *testing.T, appEntry app.AppV1) (address string, sig string) { + t.Helper() + + key, err := crypto.GenerateKey() + require.NoError(t, err) + + addr := strings.ToLower(crypto.PubkeyToAddress(key.PublicKey).Hex()) + privKeyHex := hexutil.Encode(crypto.FromECDSA(key)) + + signer, err := sign.NewEthereumMsgSigner(privKeyHex) + require.NoError(t, err) + + // Update the entry with the real address for packing + appEntry.OwnerWallet = addr + packed, err := app.PackAppV1(appEntry) + require.NoError(t, err) + + sigBytes, err := signer.Sign(packed) + require.NoError(t, err) + + return addr, hexutil.Encode(sigBytes) +} + +func newHandlerWithDefaults(store Store) *Handler { + storeTxProvider := func(fn StoreTxHandler) error { + return fn(store) + } + return NewHandler(store, storeTxProvider, &MockActionGateway{}, 4096) +} + +func TestSubmitAppVersion_Success(t *testing.T) { + appEntry := app.AppV1{ + ID: "test-app", + Metadata: "0x0000000000000000000000000000000000000000000000000000000000000000", + Version: 1, + } + + addr, sig := testOwnerWallet(t, appEntry) + + mockStore := &MockStore{ + createAppFn: func(entry app.AppV1) error { + assert.Equal(t, "test-app", entry.ID) + assert.Equal(t, addr, entry.OwnerWallet) + return nil + }, + } + + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler(mockStore, storeTxProvider, &MockActionGateway{}, 4096) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + OwnerWallet: addr, + Metadata: "0x0000000000000000000000000000000000000000000000000000000000000000", + Version: "1", + }, + OwnerSig: sig, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) +} + +func TestSubmitAppVersion_MissingOwnerWallet(t *testing.T) { + mockStore := &MockStore{} + handler := newHandlerWithDefaults(mockStore) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + Metadata: "0x00", + Version: "1", + }, + OwnerSig: "0xdeadbeef", + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "owner_wallet") +} + +func TestSubmitAppVersion_MissingOwnerSig(t *testing.T) { + mockStore := &MockStore{} + handler := newHandlerWithDefaults(mockStore) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x00", + Version: "1", + }, + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "owner_sig") +} + +func TestSubmitAppVersion_InvalidAppID(t *testing.T) { + mockStore := &MockStore{} + handler := newHandlerWithDefaults(mockStore) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "INVALID_APP!!", // doesn't match regex + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x00", + Version: "1", + }, + OwnerSig: "0xdeadbeef", + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid app ID") +} + +func TestSubmitAppVersion_InvalidVersion(t *testing.T) { + mockStore := &MockStore{} + handler := newHandlerWithDefaults(mockStore) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x00", + Version: "2", // Only version 1 is supported + }, + OwnerSig: "0xdeadbeef", + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "version 1") +} + +func TestSubmitAppVersion_InvalidSignature(t *testing.T) { + mockStore := &MockStore{} + handler := newHandlerWithDefaults(mockStore) + + reqPayload := rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x0000000000000000000000000000000000000000000000000000000000000000", + Version: "1", + }, + OwnerSig: "0x" + strings.Repeat("ab", 65), // valid hex, wrong signature + } + + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, string(rpc.AppsV1SubmitAppVersionMethod), payload), + } + + handler.SubmitAppVersion(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid owner signature") +} diff --git a/clearnode/api/apps_v1/testing.go b/clearnode/api/apps_v1/testing.go new file mode 100644 index 000000000..e3cf9a4cf --- /dev/null +++ b/clearnode/api/apps_v1/testing.go @@ -0,0 +1,58 @@ +package apps_v1 + +import ( + "time" + + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" +) + +// MockStore implements the Store interface for testing. +type MockStore struct { + createAppFn func(entry app.AppV1) error + getAppsFn func(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) +} + +func (m *MockStore) CreateApp(entry app.AppV1) error { + if m.createAppFn != nil { + return m.createAppFn(entry) + } + return nil +} + +func (m *MockStore) GetApps(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + if m.getAppsFn != nil { + return m.getAppsFn(appID, ownerWallet, pagination) + } + return nil, core.PaginationMetadata{}, nil +} + +func (m *MockStore) GetAppCount(_ string) (uint64, error) { + return 0, nil +} + +func (m *MockStore) GetTotalUserStaked(_ string) (decimal.Decimal, error) { + return decimal.Zero, nil +} + +func (m *MockStore) RecordAction(_ string, _ core.GatedAction) error { + return nil +} + +func (m *MockStore) GetUserActionCount(_ string, _ core.GatedAction, _ time.Duration) (uint64, error) { + return 0, nil +} + +func (m *MockStore) GetUserActionCounts(_ string, _ time.Duration) (map[core.GatedAction]uint64, error) { + return nil, nil +} + +type MockActionGateway struct { + Err error +} + +func (m *MockActionGateway) AllowAppRegistration(_ action_gateway.Store, _ string) error { + return m.Err +} diff --git a/clearnode/api/channel_v1/get_channels.go b/clearnode/api/channel_v1/get_channels.go index 9c751de07..f51d11043 100644 --- a/clearnode/api/channel_v1/get_channels.go +++ b/clearnode/api/channel_v1/get_channels.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func channelStatusFromString(s string) (core.ChannelStatus, error) { diff --git a/clearnode/api/channel_v1/get_channels_test.go b/clearnode/api/channel_v1/get_channels_test.go index aa8b1f593..05dc01c8d 100644 --- a/clearnode/api/channel_v1/get_channels_test.go +++ b/clearnode/api/channel_v1/get_channels_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func newGetChannelsHandler(mockTxStore *MockStore) *Handler { @@ -31,6 +31,7 @@ func newGetChannelsHandler(mockTxStore *MockStore) *Handler { minChallenge: uint32(3600), metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } } diff --git a/clearnode/api/channel_v1/get_escrow_channel.go b/clearnode/api/channel_v1/get_escrow_channel.go index 1866508ca..351e566c4 100644 --- a/clearnode/api/channel_v1/get_escrow_channel.go +++ b/clearnode/api/channel_v1/get_escrow_channel.go @@ -1,8 +1,8 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetEscrowChannel retrieves current on-chain escrow channel information. diff --git a/clearnode/api/channel_v1/get_escrow_channel_test.go b/clearnode/api/channel_v1/get_escrow_channel_test.go index 3232fe3be..16ae76e61 100644 --- a/clearnode/api/channel_v1/get_escrow_channel_test.go +++ b/clearnode/api/channel_v1/get_escrow_channel_test.go @@ -7,9 +7,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetEscrowChannel_Success(t *testing.T) { @@ -37,6 +37,7 @@ func TestGetEscrowChannel_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data diff --git a/clearnode/api/channel_v1/get_home_channel.go b/clearnode/api/channel_v1/get_home_channel.go index 392e44d2e..18b4d6935 100644 --- a/clearnode/api/channel_v1/get_home_channel.go +++ b/clearnode/api/channel_v1/get_home_channel.go @@ -1,8 +1,8 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetHomeChannel retrieves current on-chain home channel information. diff --git a/clearnode/api/channel_v1/get_home_channel_test.go b/clearnode/api/channel_v1/get_home_channel_test.go index 9f1198cc5..aec4fb3dd 100644 --- a/clearnode/api/channel_v1/get_home_channel_test.go +++ b/clearnode/api/channel_v1/get_home_channel_test.go @@ -7,9 +7,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetHomeChannel_Success(t *testing.T) { @@ -37,6 +37,7 @@ func TestGetHomeChannel_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data @@ -124,6 +125,7 @@ func TestGetHomeChannel_NotFound(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data diff --git a/clearnode/api/channel_v1/get_last_key_states.go b/clearnode/api/channel_v1/get_last_key_states.go index 37836d759..001d96008 100644 --- a/clearnode/api/channel_v1/get_last_key_states.go +++ b/clearnode/api/channel_v1/get_last_key_states.go @@ -1,9 +1,9 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetLastKeyStates retrieves the latest channel session key states for a user with optional filtering by session key. diff --git a/clearnode/api/channel_v1/get_latest_state.go b/clearnode/api/channel_v1/get_latest_state.go index 0d9b2a49b..80000a38c 100644 --- a/clearnode/api/channel_v1/get_latest_state.go +++ b/clearnode/api/channel_v1/get_latest_state.go @@ -1,8 +1,8 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetLatestState retrieves the current state of the user stored on the Node. diff --git a/clearnode/api/channel_v1/get_latest_state_test.go b/clearnode/api/channel_v1/get_latest_state_test.go index 837547cc3..922bca4c8 100644 --- a/clearnode/api/channel_v1/get_latest_state_test.go +++ b/clearnode/api/channel_v1/get_latest_state_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetLatestState_Success(t *testing.T) { @@ -38,6 +38,7 @@ func TestGetLatestState_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data @@ -136,6 +137,7 @@ func TestGetLatestState_OnlySigned(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data diff --git a/clearnode/api/channel_v1/handler.go b/clearnode/api/channel_v1/handler.go index d9086b6b2..f5912303c 100644 --- a/clearnode/api/channel_v1/handler.go +++ b/clearnode/api/channel_v1/handler.go @@ -3,16 +3,17 @@ package channel_v1 import ( "context" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // Handler manages channel state transitions and provides RPC endpoints for state submission. type Handler struct { useStoreInTx StoreTxProvider memoryStore MemoryStore + actionGateway ActionGateway nodeSigner *core.ChannelDefaultSigner stateAdvancer core.StateAdvancer statePacker core.StatePacker @@ -26,6 +27,7 @@ type Handler struct { func NewHandler( useStoreInTx StoreTxProvider, memoryStore MemoryStore, + actionGateway ActionGateway, nodeSigner *core.ChannelDefaultSigner, stateAdvancer core.StateAdvancer, statePacker core.StatePacker, @@ -39,6 +41,7 @@ func NewHandler( statePacker: statePacker, useStoreInTx: useStoreInTx, memoryStore: memoryStore, + actionGateway: actionGateway, nodeSigner: nodeSigner, nodeAddress: nodeAddress, minChallenge: minChallenge, diff --git a/clearnode/api/channel_v1/interface.go b/clearnode/api/channel_v1/interface.go index 28206873c..3817e275a 100644 --- a/clearnode/api/channel_v1/interface.go +++ b/clearnode/api/channel_v1/interface.go @@ -1,7 +1,8 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) @@ -78,6 +79,13 @@ type Store interface { // exists at its latest version for the (wallet, sessionKey) pair, includes the given asset, // and matches the metadata hash. ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, metadataHash string) (bool, error) + + action_gateway.Store +} + +type ActionGateway interface { + // AllowAction checks if a user is allowed to perform a specific gated action based on their past activity and allowances. + AllowAction(tx action_gateway.Store, userAddress string, gatedAction core.GatedAction) error } // SigValidator validates cryptographic signatures on state transitions. diff --git a/clearnode/api/channel_v1/request_creation.go b/clearnode/api/channel_v1/request_creation.go index 81a419e68..99fd79fa1 100644 --- a/clearnode/api/channel_v1/request_creation.go +++ b/clearnode/api/channel_v1/request_creation.go @@ -4,11 +4,11 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // RequestCreation processes channel creation requests from users. diff --git a/clearnode/api/channel_v1/request_creation_test.go b/clearnode/api/channel_v1/request_creation_test.go index f43810d0a..1db939081 100644 --- a/clearnode/api/channel_v1/request_creation_test.go +++ b/clearnode/api/channel_v1/request_creation_test.go @@ -10,9 +10,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestRequestCreation_Success(t *testing.T) { @@ -42,6 +42,7 @@ func TestRequestCreation_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -191,6 +192,7 @@ func TestRequestCreation_Acknowledgement_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -332,6 +334,7 @@ func TestRequestCreation_InvalidChallenge(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data diff --git a/clearnode/api/channel_v1/submit_session_key_state.go b/clearnode/api/channel_v1/submit_session_key_state.go index 16eb612ea..3f464d367 100644 --- a/clearnode/api/channel_v1/submit_session_key_state.go +++ b/clearnode/api/channel_v1/submit_session_key_state.go @@ -5,9 +5,9 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // SubmitSessionKeyState processes channel session key state submissions for registration and updates. diff --git a/clearnode/api/channel_v1/submit_state.go b/clearnode/api/channel_v1/submit_state.go index 1f3758b6b..2daf5867d 100644 --- a/clearnode/api/channel_v1/submit_state.go +++ b/clearnode/api/channel_v1/submit_state.go @@ -1,10 +1,10 @@ package channel_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" ) // SubmitState processes user-submitted state transitions, validates them against the current state, @@ -30,7 +30,12 @@ func (h *Handler) SubmitState(c *rpc.Context) { var nodeSig string incomingTransition := incomingState.Transition err = h.useStoreInTx(func(tx Store) error { - _, err := tx.LockUserState(incomingState.UserWallet, incomingState.Asset) + err := h.actionGateway.AllowAction(tx, incomingState.UserWallet, incomingState.Transition.Type.GatedAction()) + if err != nil { + return rpc.NewError(err) + } + + _, err = tx.LockUserState(incomingState.UserWallet, incomingState.Asset) if err != nil { return rpc.Errorf("failed to lock user state: %v", err) } diff --git a/clearnode/api/channel_v1/submit_state_test.go b/clearnode/api/channel_v1/submit_state_test.go index 73bd45ab1..75407f1d1 100644 --- a/clearnode/api/channel_v1/submit_state_test.go +++ b/clearnode/api/channel_v1/submit_state_test.go @@ -10,9 +10,9 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestSubmitState_TransferSend_Success(t *testing.T) { @@ -42,6 +42,7 @@ func TestSubmitState_TransferSend_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive senderWallet from a user signer key @@ -216,6 +217,7 @@ func TestSubmitState_EscrowLock_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -371,6 +373,7 @@ func TestSubmitState_EscrowWithdraw_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -524,6 +527,7 @@ func TestSubmitState_HomeDeposit_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -655,6 +659,7 @@ func TestSubmitState_HomeWithdrawal_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -788,6 +793,7 @@ func TestSubmitState_MutualLock_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -943,6 +949,7 @@ func TestSubmitState_EscrowDeposit_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -1098,6 +1105,7 @@ func TestSubmitState_Finalize_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key @@ -1237,6 +1245,7 @@ func TestSubmitState_Acknowledgement_Success(t *testing.T) { minChallenge: minChallenge, metrics: metrics.NewNoopRuntimeMetricExporter(), maxSessionKeyIDs: 256, + actionGateway: &MockActionGateway{}, } // Test data - derive userWallet from a user signer key diff --git a/clearnode/api/channel_v1/testing.go b/clearnode/api/channel_v1/testing.go index 198f3efb0..78c3ab245 100644 --- a/clearnode/api/channel_v1/testing.go +++ b/clearnode/api/channel_v1/testing.go @@ -1,13 +1,16 @@ package channel_v1 import ( + "time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" "github.com/stretchr/testify/mock" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" ) // MockStore is a mock implementation of the Store interface @@ -111,6 +114,34 @@ func (m *MockStore) ValidateChannelSessionKeyForAsset(wallet, sessionKey, asset, return args.Bool(0), args.Error(1) } +func (m *MockStore) GetAppCount(ownerWallet string) (uint64, error) { + args := m.Called(ownerWallet) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockStore) GetTotalUserStaked(wallet string) (decimal.Decimal, error) { + args := m.Called(wallet) + return args.Get(0).(decimal.Decimal), args.Error(1) +} + +func (m *MockStore) RecordAction(wallet string, gatedAction core.GatedAction) error { + args := m.Called(wallet, gatedAction) + return args.Error(0) +} + +func (m *MockStore) GetUserActionCount(wallet string, gatedAction core.GatedAction, window time.Duration) (uint64, error) { + args := m.Called(wallet, gatedAction, window) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockStore) GetUserActionCounts(userWallet string, window time.Duration) (map[core.GatedAction]uint64, error) { + args := m.Called(userWallet, window) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(map[core.GatedAction]uint64), args.Error(1) +} + func NewMockSigner() sign.Signer { key, _ := crypto.GenerateKey() @@ -171,3 +202,11 @@ func (m *MockStatePacker) PackState(state core.State) ([]byte, error) { args := m.Called(state) return args.Get(0).([]byte), args.Error(1) } + +type MockActionGateway struct { + Err error +} + +func (m *MockActionGateway) AllowAction(_ action_gateway.Store, _ string, _ core.GatedAction) error { + return m.Err +} diff --git a/clearnode/api/channel_v1/utils.go b/clearnode/api/channel_v1/utils.go index 7346365fa..d7e85bb72 100644 --- a/clearnode/api/channel_v1/utils.go +++ b/clearnode/api/channel_v1/utils.go @@ -8,8 +8,8 @@ import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func toCoreState(state rpc.StateV1) (core.State, error) { diff --git a/clearnode/api/metric_store.go b/clearnode/api/metric_store.go index b9a84117c..2ea3e7601 100644 --- a/clearnode/api/metric_store.go +++ b/clearnode/api/metric_store.go @@ -1,10 +1,10 @@ package api import ( - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" ) // metricStore wraps a DatabaseStore to buffer metric callbacks during a DB transaction. @@ -40,7 +40,7 @@ func (s *metricStore) UpdateAppSession(session app.AppSessionV1) error { return err } s.callbacks = append(s.callbacks, func() { - s.m.IncAppStateUpdate(session.Application) + s.m.IncAppStateUpdate(session.ApplicationID) }) return nil } diff --git a/clearnode/api/node_v1/get_assets.go b/clearnode/api/node_v1/get_assets.go index 392fb6ea3..290af89d6 100644 --- a/clearnode/api/node_v1/get_assets.go +++ b/clearnode/api/node_v1/get_assets.go @@ -3,7 +3,7 @@ package node_v1 import ( "strconv" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetAssets retrieves the current assets of the Node. diff --git a/clearnode/api/node_v1/get_assets_test.go b/clearnode/api/node_v1/get_assets_test.go index 4e5495116..caae73999 100644 --- a/clearnode/api/node_v1/get_assets_test.go +++ b/clearnode/api/node_v1/get_assets_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetAssets_Success(t *testing.T) { diff --git a/clearnode/api/node_v1/get_config.go b/clearnode/api/node_v1/get_config.go index 91c80011f..050333c82 100644 --- a/clearnode/api/node_v1/get_config.go +++ b/clearnode/api/node_v1/get_config.go @@ -1,8 +1,8 @@ package node_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetConfig retrieves the current configuration of the Node. diff --git a/clearnode/api/node_v1/get_config_test.go b/clearnode/api/node_v1/get_config_test.go index dacae7233..c113a4a1d 100644 --- a/clearnode/api/node_v1/get_config_test.go +++ b/clearnode/api/node_v1/get_config_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetConfig_Success(t *testing.T) { diff --git a/clearnode/api/node_v1/interface.go b/clearnode/api/node_v1/interface.go index 140da7077..34f3f7074 100644 --- a/clearnode/api/node_v1/interface.go +++ b/clearnode/api/node_v1/interface.go @@ -1,7 +1,7 @@ package node_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // MemoryStore defines an in-memory data store interface for retrieving diff --git a/clearnode/api/node_v1/ping.go b/clearnode/api/node_v1/ping.go index 1a0a70e94..8de7c358e 100644 --- a/clearnode/api/node_v1/ping.go +++ b/clearnode/api/node_v1/ping.go @@ -1,7 +1,7 @@ package node_v1 import ( - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) // Ping handles the ping request and responds with the ping method. diff --git a/clearnode/api/node_v1/testing.go b/clearnode/api/node_v1/testing.go index f02577d70..cf114d19f 100644 --- a/clearnode/api/node_v1/testing.go +++ b/clearnode/api/node_v1/testing.go @@ -3,7 +3,7 @@ package node_v1 import ( "github.com/stretchr/testify/mock" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // MockMemoryStore is a mock implementation of the MemoryStore interface diff --git a/clearnode/api/node_v1/utils.go b/clearnode/api/node_v1/utils.go index 2d3a3800d..378dd5545 100644 --- a/clearnode/api/node_v1/utils.go +++ b/clearnode/api/node_v1/utils.go @@ -4,15 +4,16 @@ import ( "fmt" "strconv" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func mapBlockchainV1(blockchain core.Blockchain) rpc.BlockchainInfoV1 { return rpc.BlockchainInfoV1{ - Name: blockchain.Name, - BlockchainID: strconv.FormatUint(blockchain.ID, 10), - ChannelHubAddress: blockchain.ChannelHubAddress, + Name: blockchain.Name, + BlockchainID: strconv.FormatUint(blockchain.ID, 10), + ChannelHubAddress: blockchain.ChannelHubAddress, + LockingContractAddress: blockchain.LockingContractAddress, } } diff --git a/clearnode/api/rate_limits.go b/clearnode/api/rate_limits.go new file mode 100644 index 000000000..55041e2c4 --- /dev/null +++ b/clearnode/api/rate_limits.go @@ -0,0 +1,51 @@ +package api + +import ( + "time" + + "github.com/layer-3/nitrolite/pkg/rpc" +) + +const ( + // rateLimitStorageKey is the key used to store the token bucket in connection storage. + rateLimitStorageKey = "rate_limiter" +) + +// tokenBucket holds the mutable state for per-connection rate limiting. +type tokenBucket struct { + tokens float64 + last time.Time +} + +// RateLimitMiddleware enforces per-connection rate limiting using a token bucket algorithm. +// It stores the token bucket in the connection's Storage for persistence across requests. +func (r *RPCRouter) RateLimitMiddleware(c *rpc.Context) { + bucket := &tokenBucket{ + tokens: r.rateLimitBurst, + last: time.Now().Add(-time.Second), + } + if val, ok := c.Storage.Get(rateLimitStorageKey); ok { + if b, ok := val.(*tokenBucket); ok { + bucket = b + } + } + + now := time.Now() + elapsed := now.Sub(bucket.last).Seconds() + bucket.last = now + + // Refill tokens based on elapsed time + bucket.tokens += elapsed * r.rateLimitPerSec + if bucket.tokens > r.rateLimitBurst { + bucket.tokens = r.rateLimitBurst + } + + if bucket.tokens < 1 { + c.Fail(nil, "rate limit exceeded") + return + } + bucket.tokens-- + c.Storage.Set(rateLimitStorageKey, bucket) + + c.Next() +} diff --git a/clearnode/api/rate_limits_test.go b/clearnode/api/rate_limits_test.go new file mode 100644 index 000000000..45cb7c299 --- /dev/null +++ b/clearnode/api/rate_limits_test.go @@ -0,0 +1,161 @@ +package api + +import ( + "testing" + "time" + + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRateLimitMiddleware(t *testing.T) { + t.Parallel() + + newTestRouter := func(ratePerSec, burst float64) *RPCRouter { + return &RPCRouter{ + rateLimitPerSec: ratePerSec, + rateLimitBurst: burst, + } + } + + newTestContext := func(storage *rpc.SafeStorage, requestID uint64) *rpc.Context { + return &rpc.Context{ + Storage: storage, + Request: rpc.Message{RequestID: requestID}, + } + } + + isRateLimited := func(ctx *rpc.Context) bool { + err := ctx.Response.Error() + return err != nil && err.Error() == "rate limit exceeded" + } + + t.Run("allows requests within burst limit", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(10, 5) + storage := rpc.NewSafeStorage() + + var allowed int + for i := 0; i < 5; i++ { + ctx := newTestContext(storage, uint64(i)) + router.RateLimitMiddleware(ctx) + if !isRateLimited(ctx) { + allowed++ + } + } + + assert.Equal(t, 5, allowed, "all requests within burst should be allowed") + }) + + t.Run("blocks requests exceeding burst", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(10, 3) + storage := rpc.NewSafeStorage() + + var allowed, rateLimited int + for i := 0; i < 5; i++ { + ctx := newTestContext(storage, uint64(i)) + router.RateLimitMiddleware(ctx) + if isRateLimited(ctx) { + rateLimited++ + } else { + allowed++ + } + } + + assert.Equal(t, 3, allowed, "only burst amount of requests should be allowed") + assert.Equal(t, 2, rateLimited, "excess requests should be rate limited") + }) + + t.Run("returns rate limit error message", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(10, 1) + storage := rpc.NewSafeStorage() + + // First request - allowed + ctx1 := newTestContext(storage, 1) + router.RateLimitMiddleware(ctx1) + require.False(t, isRateLimited(ctx1), "first request should be allowed") + + // Second request - should be rate limited + ctx2 := newTestContext(storage, 2) + router.RateLimitMiddleware(ctx2) + + require.True(t, isRateLimited(ctx2), "second request should be rate limited") + assert.Equal(t, "rate limit exceeded", ctx2.Response.Error().Error()) + }) + + t.Run("tokens refill over time", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(100, 2) // 100 tokens per second = 1 token per 10ms + storage := rpc.NewSafeStorage() + + // Exhaust the bucket + for i := 0; i < 2; i++ { + ctx := newTestContext(storage, uint64(i)) + router.RateLimitMiddleware(ctx) + require.False(t, isRateLimited(ctx), "initial requests should be allowed") + } + + // This should be rate limited + ctx := newTestContext(storage, 100) + router.RateLimitMiddleware(ctx) + require.True(t, isRateLimited(ctx), "should be rate limited after exhausting bucket") + + // Wait for tokens to refill (need 1 token, at 100/sec = 10ms per token) + time.Sleep(15 * time.Millisecond) + + // Now it should work + ctx = newTestContext(storage, 101) + router.RateLimitMiddleware(ctx) + assert.False(t, isRateLimited(ctx), "should be allowed after refill") + }) + + t.Run("separate storage has separate buckets", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(10, 2) + storage1 := rpc.NewSafeStorage() + storage2 := rpc.NewSafeStorage() + + // Exhaust storage1's bucket + for i := 0; i < 2; i++ { + ctx := newTestContext(storage1, uint64(i)) + router.RateLimitMiddleware(ctx) + } + + // storage1 should be rate limited + ctx1 := newTestContext(storage1, 100) + router.RateLimitMiddleware(ctx1) + assert.True(t, isRateLimited(ctx1), "storage1 should be rate limited") + + // storage2 should still have its own bucket + ctx2 := newTestContext(storage2, 200) + router.RateLimitMiddleware(ctx2) + assert.False(t, isRateLimited(ctx2), "storage2 should have its own bucket") + }) + + t.Run("bucket persists in storage", func(t *testing.T) { + t.Parallel() + + router := newTestRouter(10, 5) + storage := rpc.NewSafeStorage() + + // Make one request + ctx := newTestContext(storage, 1) + router.RateLimitMiddleware(ctx) + + // Check bucket is stored + val, ok := storage.Get(rateLimitStorageKey) + require.True(t, ok, "bucket should be stored") + + bucket, ok := val.(*tokenBucket) + require.True(t, ok, "stored value should be a tokenBucket") + assert.Less(t, bucket.tokens, 5.0, "tokens should have been consumed") + }) +} diff --git a/clearnode/api/rpc_router.go b/clearnode/api/rpc_router.go index 220b55fea..73a4e2274 100644 --- a/clearnode/api/rpc_router.go +++ b/clearnode/api/rpc_router.go @@ -3,43 +3,64 @@ package api import ( "time" - "github.com/erc7824/nitrolite/clearnode/api/app_session_v1" - "github.com/erc7824/nitrolite/clearnode/api/channel_v1" - "github.com/erc7824/nitrolite/clearnode/api/node_v1" - "github.com/erc7824/nitrolite/clearnode/api/user_v1" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/clearnode/store/memory" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/clearnode/api/app_session_v1" + "github.com/layer-3/nitrolite/clearnode/api/apps_v1" + "github.com/layer-3/nitrolite/clearnode/api/channel_v1" + "github.com/layer-3/nitrolite/clearnode/api/node_v1" + "github.com/layer-3/nitrolite/clearnode/api/user_v1" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/clearnode/store/memory" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" ) type RPCRouter struct { Node rpc.Node lg log.Logger runtimeMetrics metrics.RuntimeMetricExporter + + rateLimitPerSec float64 + rateLimitBurst float64 +} + +type RPCRouterConfig struct { + NodeVersion string + MinChallenge uint32 + + MaxParticipants int + MaxSessionDataLen int + MaxAppMetadataLen int + MaxRebalanceSignedUpdates int + MaxSessionKeyIDs int + + RateLimitPerSec float64 + RateLimitBurst float64 } func NewRPCRouter( - nodeVersion string, - minChallenge uint32, + cfg RPCRouterConfig, node rpc.Node, signer sign.Signer, dbStore database.DatabaseStore, memoryStore memory.MemoryStore, + actionGateway *action_gateway.ActionGateway, runtimeMetrics metrics.RuntimeMetricExporter, logger log.Logger, - maxParticipants, maxSessionDataLen, maxRebalanceSignedUpdates, maxSessionKeyIDs int, ) *RPCRouter { r := &RPCRouter{ - Node: node, - lg: logger.WithName("rpc-router"), - runtimeMetrics: runtimeMetrics, + Node: node, + lg: logger.WithName("rpc-router"), + runtimeMetrics: runtimeMetrics, + rateLimitPerSec: cfg.RateLimitPerSec, + rateLimitBurst: cfg.RateLimitBurst, } r.Node.Use(r.ObservabilityMiddleware) + r.Node.Use(r.RateLimitMiddleware) // Transaction wrapper helpers for each store type. // wrapWithMetrics executes fn inside a DB transaction with a metricStore wrapper, @@ -61,6 +82,12 @@ func NewRPCRouter( useAppSessionV1StoreInTx := func(h app_session_v1.StoreTxHandler) error { return wrapWithMetrics(func(ms *metricStore) error { return h(ms) }) } + useAppV1StoreInTx := func(h apps_v1.StoreTxHandler) error { + return wrapWithMetrics(func(ms *metricStore) error { return h(ms) }) + } + useUserV1StoreInTx := func(h user_v1.StoreTxHandler) error { + return wrapWithMetrics(func(ms *metricStore) error { return h(ms) }) + } nodeAddress := signer.PublicKey().Address().String() @@ -72,11 +99,12 @@ func NewRPCRouter( panic("failed to create channel wallet signer: " + err.Error()) } - channelV1Handler := channel_v1.NewHandler(useChannelV1StoreInTx, memoryStore, nodeChannelSigner, stateAdvancer, statePacker, nodeAddress, minChallenge, runtimeMetrics, maxSessionKeyIDs) - appSessionV1Handler := app_session_v1.NewHandler(useAppSessionV1StoreInTx, memoryStore, signer, stateAdvancer, statePacker, nodeAddress, runtimeMetrics, - maxParticipants, maxSessionDataLen, maxSessionKeyIDs, maxRebalanceSignedUpdates) - nodeV1Handler := node_v1.NewHandler(memoryStore, nodeAddress, nodeVersion) - userV1Handler := user_v1.NewHandler(dbStore) + channelV1Handler := channel_v1.NewHandler(useChannelV1StoreInTx, memoryStore, actionGateway, nodeChannelSigner, stateAdvancer, statePacker, nodeAddress, cfg.MinChallenge, runtimeMetrics, cfg.MaxSessionKeyIDs) + appSessionV1Handler := app_session_v1.NewHandler(useAppSessionV1StoreInTx, memoryStore, actionGateway, signer, stateAdvancer, statePacker, nodeAddress, runtimeMetrics, + cfg.MaxParticipants, cfg.MaxSessionDataLen, cfg.MaxSessionKeyIDs, cfg.MaxRebalanceSignedUpdates) + appsV1Handler := apps_v1.NewHandler(dbStore, useAppV1StoreInTx, actionGateway, cfg.MaxAppMetadataLen) + nodeV1Handler := node_v1.NewHandler(memoryStore, nodeAddress, cfg.NodeVersion) + userV1Handler := user_v1.NewHandler(dbStore, useUserV1StoreInTx, actionGateway) appSessionV1Group := r.Node.NewGroup(rpc.AppSessionsV1Group.String()) appSessionV1Group.Handle(rpc.AppSessionsV1SubmitDepositStateMethod.String(), appSessionV1Handler.SubmitDepositState) @@ -86,7 +114,7 @@ func NewRPCRouter( appSessionV1Group.Handle(rpc.AppSessionsV1GetAppSessionsMethod.String(), appSessionV1Handler.GetAppSessions) appSessionV1Group.Handle(rpc.AppSessionsV1SubmitSessionKeyStateMethod.String(), appSessionV1Handler.SubmitSessionKeyState) appSessionV1Group.Handle(rpc.AppSessionsV1GetLastKeyStatesMethod.String(), appSessionV1Handler.GetLastKeyStates) - if maxRebalanceSignedUpdates >= 2 { + if cfg.MaxRebalanceSignedUpdates >= 2 { appSessionV1Group.Handle(rpc.AppSessionsV1RebalanceAppSessionsMethod.String(), appSessionV1Handler.RebalanceAppSessions) } @@ -105,9 +133,14 @@ func NewRPCRouter( nodeV1Group.Handle(rpc.NodeV1GetAssetsMethod.String(), nodeV1Handler.GetAssets) nodeV1Group.Handle(rpc.NodeV1GetConfigMethod.String(), nodeV1Handler.GetConfig) + appsV1Group := r.Node.NewGroup(rpc.AppsV1Group.String()) + appsV1Group.Handle(rpc.AppsV1GetAppsMethod.String(), appsV1Handler.GetApps) + appsV1Group.Handle(rpc.AppsV1SubmitAppVersionMethod.String(), appsV1Handler.SubmitAppVersion) + userV1Group := r.Node.NewGroup(rpc.UserV1Group.String()) userV1Group.Handle(rpc.UserV1GetBalancesMethod.String(), userV1Handler.GetBalances) userV1Group.Handle(rpc.UserV1GetTransactionsMethod.String(), userV1Handler.GetTransactions) + userV1Group.Handle(rpc.UserV1GetActionAllowancesMethod.String(), userV1Handler.GetActionAllowances) return r } diff --git a/clearnode/api/user_v1/get_action_allowances.go b/clearnode/api/user_v1/get_action_allowances.go new file mode 100644 index 000000000..5fa193a33 --- /dev/null +++ b/clearnode/api/user_v1/get_action_allowances.go @@ -0,0 +1,59 @@ +package user_v1 + +import ( + "strconv" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +// GetActionAllowances retrieves the action allowances for a user. +func (h *Handler) GetActionAllowances(c *rpc.Context) { + var req rpc.UserV1GetActionAllowancesRequest + if err := c.Request.Payload.Translate(&req); err != nil { + c.Fail(err, "failed to parse parameters") + return + } + + if req.Wallet == "" { + c.Fail(nil, "wallet is required") + return + } + + var allowances []core.ActionAllowance + err := h.useStoreInTx(func(tx Store) error { + var err error + allowances, err = h.actionGateway.GetUserAllowances(h.store, req.Wallet) + if err != nil { + return rpc.Errorf("failed to retrieve action allowances: %w", err) + } + + return nil + }) + if err != nil { + c.Fail(err, "failed to retrieve action allowances") + return + } + + rpcAllowances := make([]rpc.ActionAllowanceV1, len(allowances)) + for i, a := range allowances { + rpcAllowances[i] = rpc.ActionAllowanceV1{ + GatedAction: a.GatedAction, + TimeWindow: a.TimeWindow, + Allowance: strconv.FormatUint(a.Allowance, 10), + Used: strconv.FormatUint(a.Used, 10), + } + } + + response := rpc.UserV1GetActionAllowancesResponse{ + Allowances: rpcAllowances, + } + + payload, err := rpc.NewPayload(response) + if err != nil { + c.Fail(err, "failed to create response") + return + } + + c.Succeed(c.Request.Method, payload) +} diff --git a/clearnode/api/user_v1/get_action_allowances_test.go b/clearnode/api/user_v1/get_action_allowances_test.go new file mode 100644 index 000000000..37cbcfac3 --- /dev/null +++ b/clearnode/api/user_v1/get_action_allowances_test.go @@ -0,0 +1,145 @@ +package user_v1 + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" +) + +func TestGetActionAllowances_Success(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler( + mockStore, + storeTxProvider, + &MockActionGateway{ + Allowances: []core.ActionAllowance{ + {GatedAction: core.GatedActionTransfer, TimeWindow: "24h", Allowance: 100, Used: 5}, + {GatedAction: core.GatedActionAppSessionCreation, TimeWindow: "24h", Allowance: 50, Used: 0}, + }, + }, + ) + + reqPayload := rpc.UserV1GetActionAllowancesRequest{ + Wallet: "0x1234567890123456789012345678901234567890", + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "user.v1.get_action_allowances", payload), + } + + handler.GetActionAllowances(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var response rpc.UserV1GetActionAllowancesResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + + assert.Len(t, response.Allowances, 2) + assert.Equal(t, core.GatedActionTransfer, response.Allowances[0].GatedAction) + assert.Equal(t, "24h", response.Allowances[0].TimeWindow) + assert.Equal(t, "100", response.Allowances[0].Allowance) + assert.Equal(t, "5", response.Allowances[0].Used) + assert.Equal(t, core.GatedActionAppSessionCreation, response.Allowances[1].GatedAction) + assert.Equal(t, "50", response.Allowances[1].Allowance) + assert.Equal(t, "0", response.Allowances[1].Used) +} + +func TestGetActionAllowances_EmptyResult(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler(mockStore, storeTxProvider, &MockActionGateway{}) + + reqPayload := rpc.UserV1GetActionAllowancesRequest{ + Wallet: "0x1234567890123456789012345678901234567890", + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "user.v1.get_action_allowances", payload), + } + + handler.GetActionAllowances(ctx) + + require.NotNil(t, ctx.Response) + require.NoError(t, ctx.Response.Error()) + + var response rpc.UserV1GetActionAllowancesResponse + err = ctx.Response.Payload.Translate(&response) + require.NoError(t, err) + + assert.Empty(t, response.Allowances) +} + +func TestGetActionAllowances_MissingWallet(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler(mockStore, storeTxProvider, &MockActionGateway{}) + + reqPayload := rpc.UserV1GetActionAllowancesRequest{} + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "user.v1.get_action_allowances", payload), + } + + handler.GetActionAllowances(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "wallet is required") +} + +func TestGetActionAllowances_GatewayError(t *testing.T) { + mockStore := new(MockStore) + storeTxProvider := func(fn StoreTxHandler) error { + return fn(mockStore) + } + + handler := NewHandler(mockStore, storeTxProvider, &MockActionGateway{ + Err: fmt.Errorf("gateway failure"), + }) + + reqPayload := rpc.UserV1GetActionAllowancesRequest{ + Wallet: "0x1234567890123456789012345678901234567890", + } + payload, err := rpc.NewPayload(reqPayload) + require.NoError(t, err) + + ctx := &rpc.Context{ + Context: context.Background(), + Request: rpc.NewRequest(1, "user.v1.get_action_allowances", payload), + } + + handler.GetActionAllowances(ctx) + + require.NotNil(t, ctx.Response) + err = ctx.Response.Error() + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to retrieve action allowances") +} diff --git a/clearnode/api/user_v1/get_balances.go b/clearnode/api/user_v1/get_balances.go index b85d9a9c7..44efc99f3 100644 --- a/clearnode/api/user_v1/get_balances.go +++ b/clearnode/api/user_v1/get_balances.go @@ -1,7 +1,7 @@ package user_v1 import ( - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetBalances retrieves the balances of the user. diff --git a/clearnode/api/user_v1/get_balances_test.go b/clearnode/api/user_v1/get_balances_test.go index 4a8613eee..c07b08ccb 100644 --- a/clearnode/api/user_v1/get_balances_test.go +++ b/clearnode/api/user_v1/get_balances_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetBalances_Success(t *testing.T) { diff --git a/clearnode/api/user_v1/get_transactions.go b/clearnode/api/user_v1/get_transactions.go index 75b690002..9c7c1bd59 100644 --- a/clearnode/api/user_v1/get_transactions.go +++ b/clearnode/api/user_v1/get_transactions.go @@ -1,8 +1,8 @@ package user_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // GetTransactions retrieves transaction history for a user with optional filters. diff --git a/clearnode/api/user_v1/get_transactions_test.go b/clearnode/api/user_v1/get_transactions_test.go index 83b76d8c7..ed41441fa 100644 --- a/clearnode/api/user_v1/get_transactions_test.go +++ b/clearnode/api/user_v1/get_transactions_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestGetTransactions_Success(t *testing.T) { diff --git a/clearnode/api/user_v1/handler.go b/clearnode/api/user_v1/handler.go index 2ebdb7c52..d0d68d10a 100644 --- a/clearnode/api/user_v1/handler.go +++ b/clearnode/api/user_v1/handler.go @@ -2,14 +2,20 @@ package user_v1 // Handler manages user data operations and provides RPC endpoints. type Handler struct { - store Store + store Store + useStoreInTx StoreTxProvider + actionGateway ActionGateway } // NewHandler creates a new Handler instance with the provided dependencies. func NewHandler( store Store, + useStoreInTx StoreTxProvider, + actionGateway ActionGateway, ) *Handler { return &Handler{ - store: store, + store: store, + useStoreInTx: useStoreInTx, + actionGateway: actionGateway, } } diff --git a/clearnode/api/user_v1/interface.go b/clearnode/api/user_v1/interface.go index f8e68edde..06f085a06 100644 --- a/clearnode/api/user_v1/interface.go +++ b/clearnode/api/user_v1/interface.go @@ -1,9 +1,19 @@ package user_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/core" ) +// StoreTxHandler is a function that executes Store operations within a transaction. +// If the handler returns an error, the transaction is rolled back; otherwise it's committed. +type StoreTxHandler func(Store) error + +// StoreTxProvider wraps Store operations in a database transaction. +// It accepts a StoreTxHandler and manages transaction lifecycle (begin, commit, rollback). +// Returns an error if the handler fails or the transaction cannot be committed. +type StoreTxProvider func(StoreTxHandler) error + // Store defines the persistence layer interface for user data management. // All methods should be implemented to work within database transactions. type Store interface { @@ -17,4 +27,11 @@ type Store interface { FromTime *uint64, ToTime *uint64, Paginate *core.PaginationParams) ([]core.Transaction, core.PaginationMetadata, error) + + action_gateway.Store +} + +type ActionGateway interface { + // GetUserAllowances retrieves the action allowances for a user, which define what actions the user is permitted to perform. + GetUserAllowances(tx action_gateway.Store, userAddress string) ([]core.ActionAllowance, error) } diff --git a/clearnode/api/user_v1/testing.go b/clearnode/api/user_v1/testing.go index 98079450b..be69f2c72 100644 --- a/clearnode/api/user_v1/testing.go +++ b/clearnode/api/user_v1/testing.go @@ -1,9 +1,13 @@ package user_v1 import ( + "time" + + "github.com/shopspring/decimal" "github.com/stretchr/testify/mock" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/pkg/core" ) // MockStore is a mock implementation of the Store interface @@ -30,3 +34,32 @@ func (m *MockStore) GetUserTransactions(Wallet string, Asset *string, TxType *co } return args.Get(0).([]core.Transaction), metadata, args.Error(2) } + +func (m *MockStore) GetAppCount(_ string) (uint64, error) { + return 0, nil +} + +func (m *MockStore) GetTotalUserStaked(_ string) (decimal.Decimal, error) { + return decimal.Zero, nil +} + +func (m *MockStore) RecordAction(_ string, _ core.GatedAction) error { + return nil +} + +func (m *MockStore) GetUserActionCount(_ string, _ core.GatedAction, _ time.Duration) (uint64, error) { + return 0, nil +} + +func (m *MockStore) GetUserActionCounts(_ string, _ time.Duration) (map[core.GatedAction]uint64, error) { + return nil, nil +} + +type MockActionGateway struct { + Allowances []core.ActionAllowance + Err error +} + +func (m *MockActionGateway) GetUserAllowances(_ action_gateway.Store, _ string) ([]core.ActionAllowance, error) { + return m.Allowances, m.Err +} diff --git a/clearnode/api/user_v1/utils.go b/clearnode/api/user_v1/utils.go index 226186dbd..c8a5f0930 100644 --- a/clearnode/api/user_v1/utils.go +++ b/clearnode/api/user_v1/utils.go @@ -1,8 +1,8 @@ package user_v1 import ( - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) func mapTransactionV1(tx core.Transaction) rpc.TransactionV1 { diff --git a/clearnode/api/utils.go b/clearnode/api/utils.go index 4aa3d4b84..dfbc54469 100644 --- a/clearnode/api/utils.go +++ b/clearnode/api/utils.go @@ -1,6 +1,6 @@ package api -import "github.com/erc7824/nitrolite/pkg/rpc" +import "github.com/layer-3/nitrolite/pkg/rpc" func getMethodPath(c *rpc.Context) string { switch c.Request.Method { diff --git a/clearnode/blockchain_worker.go b/clearnode/blockchain_worker.go index 2c7c518d0..e63b4d9aa 100644 --- a/clearnode/blockchain_worker.go +++ b/clearnode/blockchain_worker.go @@ -6,9 +6,9 @@ import ( "sync" "time" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) type BlockchainWorkerStore interface { @@ -38,13 +38,13 @@ const ( type BlockchainWorker struct { blockchainID uint64 - client core.Client + client core.BlockchainClient store BlockchainWorkerStore logger log.Logger metrics MetricsExporter } -func NewBlockchainWorker(blockchainID uint64, client core.Client, store BlockchainWorkerStore, logger log.Logger, m MetricsExporter) *BlockchainWorker { +func NewBlockchainWorker(blockchainID uint64, client core.BlockchainClient, store BlockchainWorkerStore, logger log.Logger, m MetricsExporter) *BlockchainWorker { return &BlockchainWorker{ blockchainID: blockchainID, client: client, diff --git a/clearnode/chart/README.md b/clearnode/chart/README.md index 1656ae503..7ee043e87 100644 --- a/clearnode/chart/README.md +++ b/clearnode/chart/README.md @@ -17,7 +17,7 @@ Clearnode Helm chart To install the chart with the release name `my-release`: ```bash -helm install my-release git+https://github.com/erc7824/clearnode@chart?ref=main +helm install my-release git+https://github.com/layer-3/clearnode@chart?ref=main ``` The command deploys Clearnode on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. @@ -54,7 +54,7 @@ helm delete my-release | config.secretEnvs | object | `{}` | Additional environment variables to be stored in a secret | | extraLabels | object | `{}` | Additional labels to add to all resources | | fullnameOverride | string | `""` | Override the full name | -| image.repository | string | `"ghcr.io/erc7824/clearnode"` | Docker image repository | +| image.repository | string | `"ghcr.io/layer-3/clearnode"` | Docker image repository | | image.tag | string | `"0.0.1"` | Docker image tag | | imagePullSecret | string | `""` | Image pull secret name | | metrics.enabled | bool | `true` | Enable Prometheus metrics | @@ -114,7 +114,7 @@ For managing sensitive values like API keys and credentials, you can use `helm-s 3. When deploying or upgrading, reference your secrets file with the `secrets://` prefix: ```bash - helm upgrade --install my-release git+https://github.com/erc7824/clearnode@chart?ref=main \ + helm upgrade --install my-release git+https://github.com/layer-3/clearnode@chart?ref=main \ -f values.yaml \ -f secrets://secrets.yaml ``` diff --git a/clearnode/chart/README.md.gotmpl b/clearnode/chart/README.md.gotmpl index 96c24668b..352b5f66e 100644 --- a/clearnode/chart/README.md.gotmpl +++ b/clearnode/chart/README.md.gotmpl @@ -17,7 +17,7 @@ To install the chart with the release name `my-release`: ```bash -helm install my-release git+https://github.com/erc7824/clearnode@chart?ref=main +helm install my-release git+https://github.com/layer-3/clearnode@chart?ref=main ``` The command deploys Clearnode on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. @@ -61,7 +61,7 @@ For managing sensitive values like API keys and credentials, you can use `helm-s 3. When deploying or upgrading, reference your secrets file with the `secrets://` prefix: ```bash - helm upgrade --install my-release git+https://github.com/erc7824/clearnode@chart?ref=main \ + helm upgrade --install my-release git+https://github.com/layer-3/clearnode@chart?ref=main \ -f values.yaml \ -f secrets://secrets.yaml ``` diff --git a/clearnode/chart/config/prod/action_gateway.yaml b/clearnode/chart/config/prod/action_gateway.yaml new file mode 100644 index 000000000..67ac1e8bc --- /dev/null +++ b/clearnode/chart/config/prod/action_gateway.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../../../config/schemas/action_gateway_schema.yaml +level_step_tokens: "100" +app_cost: "200" +action_gates: + transfer: + free_actions_allowance: 10 + increase_per_level: 5 + app_session_creation: + free_actions_allowance: 5 + increase_per_level: 2 + app_session_operation: + free_actions_allowance: 20 + increase_per_level: 10 + app_session_deposit: + free_actions_allowance: 15 + increase_per_level: 7 + app_session_withdrawal: + free_actions_allowance: 15 + increase_per_level: 7 diff --git a/clearnode/chart/config/prod/clearnode.yaml b/clearnode/chart/config/prod/clearnode.yaml index 351caf01f..10a5a9f10 100644 --- a/clearnode/chart/config/prod/clearnode.yaml +++ b/clearnode/chart/config/prod/clearnode.yaml @@ -12,7 +12,7 @@ config: MSG_EXPIRY_TIME: "60" image: - repository: ghcr.io/erc7824/nitrolite/clearnode + repository: ghcr.io/layer-3/nitrolite/clearnode tag: 0.3.1-rc.18 service: diff --git a/clearnode/chart/config/sandbox-v1/action_gateway.yaml b/clearnode/chart/config/sandbox-v1/action_gateway.yaml new file mode 100644 index 000000000..277b774ec --- /dev/null +++ b/clearnode/chart/config/sandbox-v1/action_gateway.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../../../config/schemas/action_gateway_schema.yaml +level_step_tokens: "200" +app_cost: "2000" +action_gates: + transfer: + free_actions_allowance: 10 + increase_per_level: 5 + app_session_creation: + free_actions_allowance: 50 + increase_per_level: 5 + app_session_operation: + free_actions_allowance: 100 + increase_per_level: 15 + app_session_deposit: + free_actions_allowance: 50 + increase_per_level: 5 + app_session_withdrawal: + free_actions_allowance: 50 + increase_per_level: 10 diff --git a/clearnode/chart/config/sandbox-v1/assets.yaml b/clearnode/chart/config/sandbox-v1/assets.yaml new file mode 100644 index 000000000..20a78ce7c --- /dev/null +++ b/clearnode/chart/config/sandbox-v1/assets.yaml @@ -0,0 +1,47 @@ +assets: + - name: "Yellow USD" + symbol: "yusd" + decimals: 6 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0xD3E8Eb01Ae895262f187c4aAe936eC5c0665bbf8" + decimals: 6 + - blockchain_id: 80002 + address: "0x0827b6aAA03475A8BF59Ee1A2bD76903DDFbaDB6" + decimals: 8 + - name: "BNB" + symbol: "bnb" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0x719a00F9e8b831335F156337cEF7dC48986b2e84" + decimals: 18 + - blockchain_id: 80002 + address: "0x9d8193e5655a36FFB9CD7D88D31c91d2650896D0" + decimals: 18 + - name: "Ether" + symbol: "eth" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0x0000000000000000000000000000000000000000" + decimals: 18 + - name: "POL" + symbol: "pol" + decimals: 18 + suggested_blockchain_id: 80002 + tokens: + - blockchain_id: 80002 + address: "0x0000000000000000000000000000000000000000" + decimals: 18 + - name: "Yellow" + symbol: "yellow" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0x236eB848C95b231299B4AA9f56c73D6893462720" + decimals: 18 diff --git a/clearnode/chart/config/sandbox-v1/blockchains.yaml b/clearnode/chart/config/sandbox-v1/blockchains.yaml new file mode 100644 index 000000000..e7615d224 --- /dev/null +++ b/clearnode/chart/config/sandbox-v1/blockchains.yaml @@ -0,0 +1,12 @@ +blockchains: +- name: "ethereum_sepolia" + id: 11155111 + channel_hub_address: "0xb7bE0E2007dDF320d680942cb9e008F986E74F83" + channel_hub_sig_validators: + 1: "0x2aC63456d78Cf2E2FDAf45cbed45b5d29907f4ac" + locking_contract_address: "0x9B3D4dA5A37857F17648CC4d78Bbae0A681C02c6" +- name: "polygon_amoy" + id: 80002 + channel_hub_address: "0x55D6f0A0322606447fbc612Cf58014Faed65aF9D" + channel_hub_sig_validators: + 1: "0x87825ACa5f4B9c3dc8B5aa3352724eDF5135D892" diff --git a/clearnode/chart/config/sandbox-v1/clearnode.yaml b/clearnode/chart/config/sandbox-v1/clearnode.yaml new file mode 100644 index 000000000..5dcd61f4f --- /dev/null +++ b/clearnode/chart/config/sandbox-v1/clearnode.yaml @@ -0,0 +1,73 @@ +config: + args: ["clearnode"] + logLevel: info + database: + driver: postgres + host: pgbouncer + port: 5432 + name: clearnode_sandbox_v1 + user: clearnode_sandbox_v1_admin + gcpSaSecret: gcp-kms-signer-sa + envSecret: clearnode-secret-env + extraEnvs: + CLEARNODE_DATABASE_MAX_OPEN_CONNS: "10" + CLEARNODE_DATABASE_MAX_IDLE_CONNS: "2" + CLEARNODE_DATABASE_CONN_MAX_LIFETIME_SEC: "3600" + CLEARNODE_DATABASE_CONN_MAX_IDLE_TIME_SEC: "600" + CLEARNODE_SIGNER_TYPE: "gcp-kms" + CLEARNODE_GCP_KMS_KEY_NAME: "projects/ynet-stage/locations/europe-central2/keyRings/clearnode-signers-eu/cryptoKeys/sandbox-v1-a/cryptoKeyVersions/1" + CLEARNODE_MAX_PARTICIPANTS: "32" + CLEARNODE_MAX_SESSION_DATA_LEN: "1024" + CLEARNODE_MAX_SIGNED_UPDATES: "0" + CLEARNODE_MAX_SESSION_KEY_IDS: "256" + +image: + repository: ghcr.io/layer-3/nitrolite/clearnode + tag: v1.2.0 + +service: + http: + enabled: true + port: 7824 + path: /v1/ws + +metrics: + enabled: true + podmonitoring: + enabled: true + port: 4242 + endpoint: "/metrics" + +resources: + limits: + cpu: 2000m + memory: 2Gi + ephemeral-storage: 256Mi + requests: + cpu: 2000m + memory: 2Gi + ephemeral-storage: 256Mi + +autoscaling: + enabled: false + +networking: + externalHostname: clearnode-sandbox.yellow.org + tlsClusterIssuer: zerossl-prod + gateway: + enabled: false + ingress: + enabled: true + className: nginx + tls: + enabled: true + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "10" + nginx.ingress.kubernetes.io/proxy-buffering: "off" + +imagePullSecret: ghcr-pull + +stressTest: + enabled: false diff --git a/clearnode/chart/config/sandbox/action_gateway.yaml b/clearnode/chart/config/sandbox/action_gateway.yaml new file mode 100644 index 000000000..67ac1e8bc --- /dev/null +++ b/clearnode/chart/config/sandbox/action_gateway.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../../../config/schemas/action_gateway_schema.yaml +level_step_tokens: "100" +app_cost: "200" +action_gates: + transfer: + free_actions_allowance: 10 + increase_per_level: 5 + app_session_creation: + free_actions_allowance: 5 + increase_per_level: 2 + app_session_operation: + free_actions_allowance: 20 + increase_per_level: 10 + app_session_deposit: + free_actions_allowance: 15 + increase_per_level: 7 + app_session_withdrawal: + free_actions_allowance: 15 + increase_per_level: 7 diff --git a/clearnode/chart/config/sandbox/clearnode.yaml b/clearnode/chart/config/sandbox/clearnode.yaml index 808785df9..420ccec06 100644 --- a/clearnode/chart/config/sandbox/clearnode.yaml +++ b/clearnode/chart/config/sandbox/clearnode.yaml @@ -12,7 +12,7 @@ config: MSG_EXPIRY_TIME: "60" image: - repository: ghcr.io/erc7824/nitrolite/clearnode + repository: ghcr.io/layer-3/nitrolite/clearnode tag: 6069d30 service: diff --git a/clearnode/chart/config/uat/assets.yaml b/clearnode/chart/config/uat/assets.yaml deleted file mode 100644 index a58b93106..000000000 --- a/clearnode/chart/config/uat/assets.yaml +++ /dev/null @@ -1,19 +0,0 @@ -assets: - - name: "USD Coin" - symbol: "usdc" - tokens: - - blockchain_id: 137 - address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" - decimals: 6 - - name: "ytest.USD" - symbol: "ytest.usd" - tokens: - - blockchain_id: 11155111 - address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" - decimals: 6 - - name: "wETH" - symbol: "weth" - tokens: - - blockchain_id: 137 - address: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619" - decimals: 18 diff --git a/clearnode/chart/config/uat/blockchains.yaml b/clearnode/chart/config/uat/blockchains.yaml deleted file mode 100644 index daba54318..000000000 --- a/clearnode/chart/config/uat/blockchains.yaml +++ /dev/null @@ -1,13 +0,0 @@ -default_contract_addresses: - custody: "0x490fb189DdE3a01B00be9BA5F41e3447FbC838b6" - adjudicator: "0x7de4A0736Cf5740fD3Ca2F2e9cc85c9AC223eF0C" - -blockchains: -- name: polygon - id: 137 - contract_addresses: - balance_checker: "0x2352c63A83f9Fd126af8676146721Fa00924d7e4" -- name: ethereum_sepolia - id: 11155111 - contract_addresses: - balance_checker: "0xBfbCed302deD369855fc5f7668356e123ca4B329" diff --git a/clearnode/chart/config/uat/clearnode.yaml b/clearnode/chart/config/uat/clearnode.yaml deleted file mode 100644 index 8ab3f821f..000000000 --- a/clearnode/chart/config/uat/clearnode.yaml +++ /dev/null @@ -1,55 +0,0 @@ -config: - args: ["clearnode"] - logLevel: debug - database: - driver: postgres - host: postgresql.core - port: 5432 - name: clearnet_uat - user: clearnet_uat_admin - envSecret: "" - extraEnvs: - MSG_EXPIRY_TIME: "60" - -image: - repository: ghcr.io/erc7824/nitrolite/clearnode - tag: c9c4d4c - -service: - http: - enabled: true - port: 8000 - path: / - -metrics: - enabled: true - podmonitoring: - enabled: true - port: 4242 - endpoint: "/metrics" - -resources: - limits: - cpu: 100m - memory: 256Mi - ephemeral-storage: 100Mi - requests: - cpu: 100m - memory: 256Mi - ephemeral-storage: 100Mi - -autoscaling: - enabled: false - -networking: - externalHostname: canarynet.yellow.com - tlsClusterIssuer: zerossl-prod - gateway: - enabled: false - ingress: - enabled: true - className: nginx - tls: - enabled: true - -imagePullSecret: ghcr-pull diff --git a/clearnode/chart/config/uat/secrets.yaml b/clearnode/chart/config/uat/secrets.yaml deleted file mode 100644 index 33534c8d2..000000000 --- a/clearnode/chart/config/uat/secrets.yaml +++ /dev/null @@ -1,7 +0,0 @@ -config: - database: - password: ref+tfstategs://terraform-state-deploy/gke-uat-postgresql-admin/default.tfstate/output.postgresql_user_passwords["clearnet_uat_admin"] - secretEnvs: - BROKER_PRIVATE_KEY: ref+gcpsecrets://ynet-stage/clearnet-uat-broker-private-key?version=latest - POLYGON_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-uat-polygon-blockchain-rpc?version=latest - ETHEREUM_SEPOLIA_BLOCKCHAIN_RPC: ref+gcpsecrets://ynet-stage/clearnet-uat-eth-sepolia-blockchain-rpc?version=latest diff --git a/clearnode/chart/config/v1-rc/action_gateway.yaml b/clearnode/chart/config/v1-rc/action_gateway.yaml new file mode 100644 index 000000000..67ac1e8bc --- /dev/null +++ b/clearnode/chart/config/v1-rc/action_gateway.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../../../config/schemas/action_gateway_schema.yaml +level_step_tokens: "100" +app_cost: "200" +action_gates: + transfer: + free_actions_allowance: 10 + increase_per_level: 5 + app_session_creation: + free_actions_allowance: 5 + increase_per_level: 2 + app_session_operation: + free_actions_allowance: 20 + increase_per_level: 10 + app_session_deposit: + free_actions_allowance: 15 + increase_per_level: 7 + app_session_withdrawal: + free_actions_allowance: 15 + increase_per_level: 7 diff --git a/clearnode/chart/config/v1-rc/assets.yaml b/clearnode/chart/config/v1-rc/assets.yaml index 2290bd4c1..80c7f299e 100644 --- a/clearnode/chart/config/v1-rc/assets.yaml +++ b/clearnode/chart/config/v1-rc/assets.yaml @@ -23,4 +23,11 @@ assets: - blockchain_id: 11155111 address: "0x0000000000000000000000000000000000000000" decimals: 18 - + - name: "Yellow" + symbol: "yellow" + decimals: 18 + suggested_blockchain_id: 11155111 + tokens: + - blockchain_id: 11155111 + address: "0xB1aA0ac73B5E648a57db2d9342f11c471FcC85F1" + decimals: 18 diff --git a/clearnode/chart/config/v1-rc/blockchains.yaml b/clearnode/chart/config/v1-rc/blockchains.yaml index f338ed26d..2c63d8745 100644 --- a/clearnode/chart/config/v1-rc/blockchains.yaml +++ b/clearnode/chart/config/v1-rc/blockchains.yaml @@ -1,7 +1,7 @@ -default_contract_addresses: - channel_hub: "0x09ffB9eA86F42e3e3B5E34650311d7E595dFB769" - blockchains: - name: "ethereum_sepolia" id: 11155111 channel_hub_address: "0x09ffB9eA86F42e3e3B5E34650311d7E595dFB769" + channel_hub_sig_validators: + 1: "0xae5f5f520d0edb5c2ead31381f6d1d1fc2a7c36b" + locking_contract_address: "0x89A969AE9D7e695DF948Ba744e72A97769C5C1ef" diff --git a/clearnode/chart/config/v1-rc/clearnode.yaml b/clearnode/chart/config/v1-rc/clearnode.yaml index 3a21f8b52..b92af869c 100644 --- a/clearnode/chart/config/v1-rc/clearnode.yaml +++ b/clearnode/chart/config/v1-rc/clearnode.yaml @@ -22,7 +22,7 @@ config: CLEARNODE_MAX_SESSION_KEY_IDS: "256" image: - repository: ghcr.io/erc7824/nitrolite/clearnode + repository: ghcr.io/layer-3/nitrolite/clearnode tag: v1.0.0-rc.0 service: diff --git a/clearnode/chart/templates/configmap.yaml b/clearnode/chart/templates/configmap.yaml index fa64af12d..ff5a9d6f5 100644 --- a/clearnode/chart/templates/configmap.yaml +++ b/clearnode/chart/templates/configmap.yaml @@ -13,3 +13,5 @@ data: {{ .Values.config.blockchains | indent 4 }} assets.yaml: |- {{ .Values.config.assets | indent 4 }} + action_gateway.yaml: |- +{{ .Values.config.actionGateway | indent 4 }} diff --git a/clearnode/chart/templates/debug-deployment.yaml b/clearnode/chart/templates/debug-deployment.yaml new file mode 100644 index 000000000..7fa273de8 --- /dev/null +++ b/clearnode/chart/templates/debug-deployment.yaml @@ -0,0 +1,65 @@ +{{- if .Values.debug.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "clearnode.common.fullname" . }}-debug + labels: + {{- include "clearnode.common.labels" . | nindent 4 }} + app.kubernetes.io/component: debug +spec: + replicas: 1 + selector: + matchLabels: + {{- include "clearnode.common.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: debug + template: + metadata: + labels: + {{- include "clearnode.common.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: debug + spec: + {{- with .Values.serviceAccount }} + serviceAccountName: {{ . }} + {{- end }} + containers: + - name: debug + image: {{ include "clearnode.component.image" .Values.image }} + imagePullPolicy: IfNotPresent + command: ["sleep", "infinity"] + env: + {{- include "clearnode.common.env" . | nindent 12 }} + {{- if or .Values.config.secretEnvs .Values.config.envSecret }} + envFrom: + {{- if .Values.config.envSecret }} + - secretRef: + name: {{ .Values.config.envSecret }} + {{- end }} + {{- if .Values.config.secretEnvs }} + - secretRef: + name: {{ include "clearnode.common.fullname" . }}-secret-env + {{- end }} + {{- end }} + volumeMounts: + - name: config + mountPath: /app/config + {{- if .Values.config.gcpSaSecret }} + - name: gcp-sa + mountPath: /etc/gcp + readOnly: true + {{- end }} + resources: + {{- toYaml .Values.debug.resources | nindent 12 }} + volumes: + - name: config + configMap: + name: {{ include "clearnode.common.fullname" . }}-config + {{- if .Values.config.gcpSaSecret }} + - name: gcp-sa + secret: + secretName: {{ .Values.config.gcpSaSecret }} + {{- end }} + {{- include "clearnode.common.imagePullSecrets" . | nindent 6 }} + {{- include "clearnode.common.nodeSelectorLabels" . | nindent 6 }} + {{- include "clearnode.common.affinity" . | nindent 6 }} + {{- include "clearnode.common.tolerations" . | nindent 6 }} +{{- end }} diff --git a/clearnode/chart/templates/deployment.yaml b/clearnode/chart/templates/full-deployment.yaml similarity index 98% rename from clearnode/chart/templates/deployment.yaml rename to clearnode/chart/templates/full-deployment.yaml index 10928e5eb..26dae6772 100644 --- a/clearnode/chart/templates/deployment.yaml +++ b/clearnode/chart/templates/full-deployment.yaml @@ -4,6 +4,7 @@ metadata: name: {{ include "clearnode.common.fullname" . }} labels: {{- include "clearnode.common.labels" . | nindent 4 }} + app.kubernetes.io/component: server spec: {{- include "clearnode.component.replicaCount" . | nindent 2 }} strategy: diff --git a/clearnode/chart/templates/helpers/_ingress.tpl b/clearnode/chart/templates/helpers/_ingress.tpl index e6116e714..3fb3d2ede 100644 --- a/clearnode/chart/templates/helpers/_ingress.tpl +++ b/clearnode/chart/templates/helpers/_ingress.tpl @@ -26,6 +26,7 @@ nginx.ingress.kubernetes.io/ssl-redirect: "true" {{- if .Values.networking.ingress.grpc }} nginx.ingress.kubernetes.io/backend-protocol: "GRPC" {{- end }} +nginx.ingress.kubernetes.io/rewrite-target: /ws {{- with .Values.networking.ingress.annotations }} {{ toYaml . }} {{- end }} diff --git a/clearnode/chart/values.yaml b/clearnode/chart/values.yaml index f4e303c2c..4ba7a8b51 100644 --- a/clearnode/chart/values.yaml +++ b/clearnode/chart/values.yaml @@ -37,13 +37,15 @@ config: blockchains: "" # -- Assets configuration assets: "" + # -- Action Gateway configuration + actionGateway: "" # -- Number of replicas replicaCount: 1 image: # -- Docker image repository - repository: ghcr.io/erc7824/nitrolite/clearnode + repository: ghcr.io/layer-3/nitrolite/clearnode # -- Docker image tag tag: v1.0.0-rc.0 @@ -53,8 +55,8 @@ service: enabled: true # -- HTTP service port port: 7824 - # -- HTTP service path - path: / + # -- HTTP service path (used by ingress) + path: /ws metrics: # -- Enable Prometheus metrics @@ -150,6 +152,18 @@ affinity: {} # -- Additional labels to add to all resources extraLabels: {} +debug: + # -- Enable debug deployment (idle container for exec debugging) + enabled: false + # -- Resource requests for debug container + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi + stressTest: # -- Enable stress test pods (helm test) enabled: false diff --git a/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql b/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql index f84923a28..fd7ba50a3 100644 --- a/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql +++ b/clearnode/config/migrations/postgres/20251222000000_initial_schema.sql @@ -87,16 +87,30 @@ CREATE TABLE transactions ( ); CREATE INDEX idx_transactions_type ON transactions(tx_type); +CREATE INDEX idx_transactions_type_created ON transactions(tx_type, created_at); CREATE INDEX idx_transactions_from_account ON transactions(from_account); CREATE INDEX idx_transactions_to_account ON transactions(to_account); CREATE INDEX idx_transactions_from_to_type ON transactions(from_account, to_account, tx_type); CREATE INDEX idx_transactions_from_comp ON transactions(from_account, asset_symbol, created_at DESC); CREATE INDEX idx_transactions_to_comp ON transactions(to_account, asset_symbol, created_at DESC); +-- Application registry +CREATE TABLE apps_v1 ( + id VARCHAR(66) PRIMARY KEY, + owner_wallet CHAR(42) NOT NULL, + metadata TEXT NOT NULL, + version NUMERIC(20,0) NOT NULL DEFAULT 1, + creation_approval_not_required BOOLEAN NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_apps_v1_owner_wallet ON apps_v1(owner_wallet); + -- App Sessions table: Application sessions CREATE TABLE app_sessions_v1 ( id CHAR(66) PRIMARY KEY, - application VARCHAR NOT NULL, + application_id VARCHAR NOT NULL, nonce NUMERIC(20,0) NOT NULL, session_data TEXT NOT NULL, quorum SMALLINT NOT NULL DEFAULT 100, @@ -106,7 +120,7 @@ CREATE TABLE app_sessions_v1 ( updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -CREATE INDEX idx_app_sessions_v1_application ON app_sessions_v1(application); +CREATE INDEX idx_app_sessions_v1_application ON app_sessions_v1(application_id); CREATE INDEX idx_app_sessions_v1_status ON app_sessions_v1(status); -- App Session Participants table: Participants in application sessions @@ -122,7 +136,7 @@ CREATE INDEX idx_app_session_participants_v1_wallet ON app_session_participants_ -- App Ledger table: Internal ledger entries for application sessions CREATE TABLE app_ledger_v1 ( - id CHAR(36) PRIMARY KEY, -- UUID + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id CHAR(66) NOT NULL, -- Session ID asset_symbol VARCHAR(20) NOT NULL, wallet CHAR(42) NOT NULL, @@ -246,7 +260,55 @@ CREATE TABLE user_balances ( CREATE INDEX idx_user_balances_user_wallet ON user_balances(user_wallet); +-- User staked table: Stores staked amounts per user per blockchain +CREATE TABLE user_staked_v1 ( + user_wallet CHAR(42) NOT NULL, + blockchain_id NUMERIC(20,0) NOT NULL, + amount NUMERIC(78, 18) NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (user_wallet, blockchain_id) +); + +CREATE INDEX idx_user_staked_v1_user_wallet ON user_staked_v1(user_wallet); + +-- Action log table: Records user actions for rate limiting and auditing +CREATE TABLE action_log_v1 ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_wallet CHAR(42) NOT NULL, + gated_action SMALLINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_action_log_v1_wallet_gated_action_created ON action_log_v1(user_wallet, gated_action, created_at DESC); + +-- Lifespan metrics table: Stores accumulated metric counters with labels +CREATE TABLE lifespan_metrics ( + id VARCHAR(66) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + labels JSONB, + value NUMERIC(78, 18) NOT NULL, + last_timestamp TIMESTAMPTZ NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_lifespan_metrics_name_last_ts ON lifespan_metrics(name, last_timestamp DESC); + +-- Indexes for metric-gathering queries that filter on updated_at +CREATE INDEX idx_channels_updated_at ON channels(updated_at); +CREATE INDEX idx_app_sessions_v1_updated_at ON app_sessions_v1(updated_at); +CREATE INDEX idx_user_balances_updated_at ON user_balances(updated_at); + -- +goose Down +DROP INDEX IF EXISTS idx_user_balances_updated_at; +DROP INDEX IF EXISTS idx_app_sessions_v1_updated_at; +DROP INDEX IF EXISTS idx_channels_updated_at; +DROP INDEX IF EXISTS idx_lifespan_metrics_name_last_ts; +DROP TABLE IF EXISTS lifespan_metrics; +DROP INDEX IF EXISTS idx_action_log_v1_wallet_gated_action_created; +DROP TABLE IF EXISTS action_log_v1; +DROP INDEX IF EXISTS idx_user_staked_v1_user_wallet; +DROP TABLE IF EXISTS user_staked_v1; DROP INDEX IF EXISTS idx_user_balances_user_wallet; DROP TABLE IF EXISTS user_balances; DROP INDEX IF EXISTS idx_channel_session_key_assets_v1_asset; @@ -275,11 +337,14 @@ DROP TABLE IF EXISTS app_session_participants_v1; DROP INDEX IF EXISTS idx_app_sessions_v1_status; DROP INDEX IF EXISTS idx_app_sessions_v1_application; DROP TABLE IF EXISTS app_sessions_v1; +DROP INDEX IF EXISTS idx_apps_v1_owner_wallet; +DROP TABLE IF EXISTS apps_v1; DROP INDEX IF EXISTS idx_transactions_to_comp; DROP INDEX IF EXISTS idx_transactions_from_comp; DROP INDEX IF EXISTS idx_transactions_from_to_type; DROP INDEX IF EXISTS idx_transactions_to_account; DROP INDEX IF EXISTS idx_transactions_from_account; +DROP INDEX IF EXISTS idx_transactions_type_created; DROP INDEX IF EXISTS idx_transactions_type; DROP TABLE IF EXISTS transactions; DROP INDEX IF EXISTS idx_channel_states_escrow_channel_id; diff --git a/clearnode/config/schemas/action_gateway_schema.yaml b/clearnode/config/schemas/action_gateway_schema.yaml new file mode 100644 index 000000000..4f4997a67 --- /dev/null +++ b/clearnode/config/schemas/action_gateway_schema.yaml @@ -0,0 +1,31 @@ +$schema: "http://json-schema.org" +type: object +required: + - level_step_tokens + - app_cost + - action_gates +properties: + level_step_tokens: + type: string + pattern: "^(?:[1-9][0-9]*(\\.[0-9]+)?|0\\.[0-9]*[1-9][0-9]*)$" + description: "Decimal amount for level step tokens" + app_cost: + type: string + pattern: "^(?:[1-9][0-9]*(\\.[0-9]+)?|0\\.[0-9]*[1-9][0-9]*)$" + action_gates: + type: object + additionalProperties: + $ref: "#/definitions/ActionGateConfig" +definitions: + ActionGateConfig: + type: object + required: + - free_actions_allowance + - increase_per_level + properties: + free_actions_allowance: + type: integer + minimum: 0 + increase_per_level: + type: integer + minimum: 0 diff --git a/clearnode/event_handlers/interface.go b/clearnode/event_handlers/interface.go index 40167fb07..7b4e011ac 100644 --- a/clearnode/event_handlers/interface.go +++ b/clearnode/event_handlers/interface.go @@ -1,7 +1,8 @@ package event_handlers import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" ) // StoreTxHandler is a function that executes Store operations within a transaction. @@ -49,4 +50,7 @@ type Store interface { // ScheduleFinalizeEscrowWithdrawal schedules a checkpoint for an escrow withdrawal operation. // This queues the state to be submitted on-chain to finalize an escrow withdrawal. ScheduleFinalizeEscrowWithdrawal(stateID string, chainID uint64) error + + // UpdateUserStaked updates the total staked amount for a user on a specific blockchain. + UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error } diff --git a/clearnode/event_handlers/service.go b/clearnode/event_handlers/service.go index 46e87d6b8..ff3dbb7b1 100644 --- a/clearnode/event_handlers/service.go +++ b/clearnode/event_handlers/service.go @@ -4,11 +4,12 @@ import ( "context" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) -var _ core.BlockchainEventHandler = &EventHandlerService{} +var _ core.ChannelHubEventHandler = &EventHandlerService{} +var _ core.LockingContractEventHandler = &EventHandlerService{} // EventHandlerService processes blockchain events and updates the local database state accordingly. // It handles events from both home channels (user state channels) and escrow channels (temporary lock channels). @@ -445,3 +446,16 @@ func (s *EventHandlerService) HandleEscrowWithdrawalFinalized(ctx context.Contex return nil }) } + +func (s *EventHandlerService) HandleUserLockedBalanceUpdated(ctx context.Context, event *core.UserLockedBalanceUpdatedEvent) error { + logger := log.FromContext(ctx) + return s.useStoreInTx(func(tx Store) error { + err := tx.UpdateUserStaked(event.UserAddress, event.BlockchainID, event.Balance) + if err != nil { + return err + } + + logger.Info("handled UserLockedBalanceUpdatedEvent event", "userWallet", event.UserAddress, "blockchainID", event.BlockchainID, "balance", event.Balance) + return nil + }) +} diff --git a/clearnode/event_handlers/service_test.go b/clearnode/event_handlers/service_test.go index 2c3fed384..e5e9afb1c 100644 --- a/clearnode/event_handlers/service_test.go +++ b/clearnode/event_handlers/service_test.go @@ -2,14 +2,16 @@ package event_handlers import ( "context" + "errors" "testing" "time" + "github.com/shopspring/decimal" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) func TestHandleHomeChannelCreated_Success(t *testing.T) { @@ -515,3 +517,70 @@ func TestHandleEscrowWithdrawalFinalized_Success(t *testing.T) { require.NoError(t, err) mockStore.AssertExpectations(t) } + +func TestHandleUserLockedBalanceUpdated_Success(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + } + + // Test data + userWallet := "0x1234567890123456789012345678901234567890" + blockchainID := uint64(1) + balance := decimal.NewFromInt(1000) + + event := &core.UserLockedBalanceUpdatedEvent{ + UserAddress: userWallet, + BlockchainID: blockchainID, + Balance: balance, + } + + // Mock expectations + mockStore.On("UpdateUserStaked", userWallet, blockchainID, balance).Return(nil) + + // Execute + err := service.HandleUserLockedBalanceUpdated(ctx, event) + + // Assert + require.NoError(t, err) + mockStore.AssertExpectations(t) +} + +func TestHandleUserLockedBalanceUpdated_StoreError(t *testing.T) { + // Setup + mockStore := new(MockStore) + ctx := log.SetContextLogger(context.Background(), log.NewNoopLogger()) + + service := &EventHandlerService{ + useStoreInTx: func(handler StoreTxHandler) error { + return handler(mockStore) + }, + } + + // Test data + userWallet := "0x1234567890123456789012345678901234567890" + blockchainID := uint64(1) + balance := decimal.NewFromInt(500) + + event := &core.UserLockedBalanceUpdatedEvent{ + UserAddress: userWallet, + BlockchainID: blockchainID, + Balance: balance, + } + + // Mock expectations + mockStore.On("UpdateUserStaked", userWallet, blockchainID, balance).Return(errors.New("db error")) + + // Execute + err := service.HandleUserLockedBalanceUpdated(ctx, event) + + // Assert + require.Error(t, err) + require.Contains(t, err.Error(), "db error") + mockStore.AssertExpectations(t) +} diff --git a/clearnode/event_handlers/testing.go b/clearnode/event_handlers/testing.go index 6a87572ce..aab414c32 100644 --- a/clearnode/event_handlers/testing.go +++ b/clearnode/event_handlers/testing.go @@ -1,9 +1,10 @@ package event_handlers import ( + "github.com/shopspring/decimal" "github.com/stretchr/testify/mock" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // MockStore is a mock implementation of the Store interface for testing @@ -67,3 +68,9 @@ func (m *MockStore) ScheduleFinalizeEscrowWithdrawal(stateID string, chainID uin args := m.Called(stateID, chainID) return args.Error(0) } + +// UpdateUserStaked mocks updating the total staked amount for a user +func (m *MockStore) UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error { + args := m.Called(wallet, blockchainID, amount) + return args.Error(0) +} diff --git a/clearnode/main.go b/clearnode/main.go index d34fdad40..3980fb3b1 100644 --- a/clearnode/main.go +++ b/clearnode/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "net/http" "os" "os/signal" @@ -12,13 +13,14 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/erc7824/nitrolite/clearnode/api" - "github.com/erc7824/nitrolite/clearnode/event_handlers" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/clearnode/stress" - "github.com/erc7824/nitrolite/pkg/blockchain/evm" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/clearnode/api" + "github.com/layer-3/nitrolite/clearnode/event_handlers" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/clearnode/stress" + "github.com/layer-3/nitrolite/pkg/blockchain/evm" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) func main() { @@ -26,14 +28,30 @@ func main() { os.Exit(stress.Run(os.Args[2:])) } + if len(os.Args) > 1 && os.Args[1] == "operator" { + runOperatorCommand(os.Args[2:]) + return + } + bb := InitBackbone() logger := bb.Logger - ctx := context.Background() + rootCtx := context.Background() + blockchainCtx, cancelBlockchain := context.WithCancel(rootCtx) + ctx := rootCtx vl := bb.ValidationLimits - api.NewRPCRouter(bb.NodeVersion, bb.ChannelMinChallengeDuration, - bb.RpcNode, bb.StateSigner, bb.DbStore, bb.MemoryStore, bb.RuntimeMetrics, bb.Logger, - vl.MaxParticipants, vl.MaxSessionDataLen, vl.MaxSignedUpdates, vl.MaxSessionKeyIDs) + rpcRouterCfg := api.RPCRouterConfig{ + NodeVersion: bb.NodeVersion, + MinChallenge: bb.ChannelMinChallengeDuration, + MaxParticipants: vl.MaxParticipants, + MaxSessionDataLen: vl.MaxSessionDataLen, + MaxAppMetadataLen: vl.MaxAppMetadataLen, + MaxRebalanceSignedUpdates: vl.MaxSignedUpdates, + MaxSessionKeyIDs: vl.MaxSessionKeyIDs, + RateLimitPerSec: bb.RateLimitPerSec, + RateLimitBurst: bb.RateLimitBurst, + } + api.NewRPCRouter(rpcRouterCfg, bb.RpcNode, bb.StateSigner, bb.DbStore, bb.MemoryStore, bb.ActionGateway, bb.RuntimeMetrics, bb.Logger) rpcListenAddr := ":7824" rpcListenEndpoint := "/ws" @@ -69,37 +87,71 @@ func main() { if err != nil { logger.Fatal("failed to connect to EVM Node") } - reactor := evm.NewReactor(b.ID, eventHandlerService, bb.DbStore.StoreContractEvent) - reactor.SetOnEventProcessed(bb.RuntimeMetrics.IncBlockchainEvent) - l := evm.NewListener(common.HexToAddress(b.ChannelHubAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore.GetLatestEvent) - l.Listen(ctx, func(err error) { + + if b.ChannelHubAddress != "" { + // For the node itself, the node address is the signer's address + nodeAddress := bb.StateSigner.PublicKey().Address().String() + + clientOpts := []evm.ClientOption{ + evm.ClientBalanceCheck{RequireBalanceCheck: false}, + evm.ClientAllowanceCheck{RequireAllowanceCheck: false}, + } + + blockchainClient, err := evm.NewBlockchainClient(common.HexToAddress(b.ChannelHubAddress), client, bb.TxSigner, b.ID, nodeAddress, bb.MemoryStore, clientOpts...) if err != nil { - logger.Fatal("blockchain listener stopped", "error", err, "blockchainID", b.ID) + logger.Fatal("failed to create EVM client") } - }) - // For the node itself, the node address is the signer's address - nodeAddress := bb.StateSigner.PublicKey().Address().String() + sigValidators, err := bb.MemoryStore.GetChannelSigValidators(b.ID) + if err != nil { + logger.Fatal("failed to get channel signature validators from memory store", "error", err, "blockchainID", b.ID) + } - clientOpts := []evm.ClientOption{ - evm.ClientBalanceCheck{RequireBalanceCheck: false}, - evm.ClientAllowanceCheck{RequireAllowanceCheck: false}, - } + if err := ensureSigValidatorsRegistered(blockchainClient, sigValidators, true); err != nil { + logger.Fatal("failed to ensure signature validators are registered", "error", err, "blockchainID", b.ID) + } - blockchainClient, err := evm.NewClient(common.HexToAddress(b.ChannelHubAddress), client, bb.TxSigner, b.ID, nodeAddress, bb.MemoryStore, clientOpts...) - if err != nil { - logger.Fatal("failed to create EVM client") + reactor := evm.NewChannelHubReactor(b.ID, eventHandlerService, bb.DbStore.StoreContractEvent) + reactor.SetOnEventProcessed(bb.RuntimeMetrics.IncBlockchainEvent) + l := evm.NewListener(common.HexToAddress(b.ChannelHubAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore.GetLatestEvent) + l.Listen(blockchainCtx, func(err error) { + if err != nil { + logger.Fatal("blockchain listener stopped", "error", err, "blockchainID", b.ID) + } + }) + + worker := NewBlockchainWorker(b.ID, blockchainClient, bb.DbStore, logger, bb.RuntimeMetrics) + worker.Start(blockchainCtx, func(err error) { + if err != nil { + logger.Fatal("blockchain worker stopped", "error", err, "blockchainID", b.ID) + } + }) + } else { + logger.Info("channel hub address is not configured for blockchain", "blockchainID", b.ID) } - worker := NewBlockchainWorker(b.ID, blockchainClient, bb.DbStore, logger, bb.RuntimeMetrics) - worker.Start(ctx, func(err error) { + if b.LockingContractAddress != "" { + appRegistryClient, err := evm.NewLockingClient(common.HexToAddress(b.LockingContractAddress), client, b.ID) + if err != nil { + logger.Fatal("failed to create locking client", "error", err, "blockchainID", b.ID) + } + + reactor, err := evm.NewLockingContractReactor(b.ID, eventHandlerService, appRegistryClient.GetTokenDecimals, bb.DbStore.StoreContractEvent) if err != nil { - logger.Fatal("blockchain worker stopped", "error", err, "blockchainID", b.ID) + logger.Fatal("failed to create app registry reactor", "error", err, "blockchainID", b.ID) } - }) + + reactor.SetOnEventProcessed(bb.RuntimeMetrics.IncBlockchainEvent) + l := evm.NewListener(common.HexToAddress(b.LockingContractAddress), client, b.ID, b.BlockStep, logger, reactor.HandleEvent, bb.DbStore.GetLatestEvent) + l.Listen(blockchainCtx, func(err error) { + if err != nil { + logger.Fatal("blockchain listener stopped", "error", err, "blockchainID", b.ID) + } + }) + } } - go runStoreMetricsExporter(ctx, 10*time.Second, bb.DbStore, bb.StoreMetrics, logger) + go runStoreMetricsExporter(ctx, 30*time.Second, bb.DbStore, bb.StoreMetrics, logger) metricsListenAddr := ":4242" metricsEndpoint := "/metrics" @@ -149,21 +201,115 @@ func main() { logger.Error("failed to shut down RPC server", "error", err) } + logger.Info("stopping blockchain listeners and workers") + cancelBlockchain() + // Close backbone resources if err := bb.Close(); err != nil { logger.Error("failed to close backbone resources", "error", err) } - // TODO: gracefully stop blockchain listeners and workers logger.Info("shutdown complete") } +func runOperatorCommand(args []string) { + if len(args) == 0 { + fmt.Println("Usage: clearnode operator ") + fmt.Println("Commands:") + fmt.Println(" address Print the operator address") + fmt.Println(" register-validators Register signature validators on-chain") + os.Exit(1) + } + + switch args[0] { + case "address": + runOperatorAddress() + case "register-validators": + runRegisterValidators() + default: + fmt.Printf("Unknown operator command: %s\n", args[0]) + os.Exit(1) + } +} + +func runOperatorAddress() { + bb := InitBackbone() + defer bb.Close() + + fmt.Println(bb.StateSigner.PublicKey().Address().String()) + time.Sleep(5 * time.Second) +} + +func runRegisterValidators() { + bb := InitBackbone() + defer bb.Close() + logger := bb.Logger + + blockchains, err := bb.MemoryStore.GetBlockchains() + if err != nil { + logger.Fatal("failed to get blockchains from memory store", "error", err) + } + + for _, b := range blockchains { + if b.ChannelHubAddress == "" { + continue + } + + rpcURL, ok := bb.BlockchainRPCs[b.ID] + if !ok { + logger.Fatal("no RPC URL configured for blockchain", "blockchainID", b.ID) + } + + client, err := ethclient.Dial(rpcURL) + if err != nil { + logger.Fatal("failed to connect to EVM Node", "blockchainID", b.ID) + } + + nodeAddress := bb.StateSigner.PublicKey().Address().String() + clientOpts := []evm.ClientOption{ + evm.ClientBalanceCheck{RequireBalanceCheck: false}, + evm.ClientAllowanceCheck{RequireAllowanceCheck: false}, + } + + blockchainClient, err := evm.NewBlockchainClient(common.HexToAddress(b.ChannelHubAddress), client, bb.TxSigner, b.ID, nodeAddress, bb.MemoryStore, clientOpts...) + if err != nil { + logger.Fatal("failed to create EVM client", "blockchainID", b.ID) + } + + sigValidators, err := bb.MemoryStore.GetChannelSigValidators(b.ID) + if err != nil { + logger.Fatal("failed to get channel signature validators from memory store", "error", err, "blockchainID", b.ID) + } + + if err := ensureSigValidatorsRegistered(blockchainClient, sigValidators, false); err != nil { + logger.Fatal("failed to register signature validators", "error", err, "blockchainID", b.ID) + } + + logger.Info("signature validators registered successfully", "blockchainID", b.ID) + } + + logger.Info("all signature validators registered") +} + +func ensureSigValidatorsRegistered(client core.BlockchainClient, validators map[uint8]string, checkOnly bool) error { + for id, addr := range validators { + if err := client.EnsureSigValidatorRegistered(id, addr, checkOnly); err != nil { + return err + } + } + + return nil +} + func runStoreMetricsExporter( ctx context.Context, fetchInterval time.Duration, store interface { - CountAppSessionsByStatus() ([]database.AppSessionCount, error) - CountChannelsByStatus() ([]database.ChannelCount, error) + GetChannelsCountByLabels() ([]database.ChannelCount, error) + GetAppSessionsCountByLabels() ([]database.AppSessionCount, error) + GetTotalValueLocked() ([]database.TotalValueLocked, error) + CountActiveUsers(window time.Duration) ([]database.ActiveCountByLabel, error) + CountActiveAppSessions(window time.Duration) ([]database.ActiveCountByLabel, error) }, metricExported metrics.StoreMetricExporter, logger log.Logger) { logger = logger.WithName("store-metrics") @@ -173,21 +319,62 @@ func runStoreMetricsExporter( for { select { case <-ticker.C: - if counts, err := store.CountAppSessionsByStatus(); err != nil { - logger.Error("failed to count app sessions", "error", err) + timeSpans := []struct { + label string + duration time.Duration + }{ + {"day", 24 * time.Hour}, + {"week", 7 * 24 * time.Hour}, + {"month", 30 * 24 * time.Hour}, + } + + channelCounts, err := store.GetChannelsCountByLabels() + if err != nil { + logger.Error("failed to get channel counts by labels", "error", err) + } else { + for _, c := range channelCounts { + metricExported.SetChannels(c.Asset, c.Status, c.Count) + } + } + + appSessionCounts, err := store.GetAppSessionsCountByLabels() + if err != nil { + logger.Error("failed to get app sessions counts by labels", "error", err) } else { - for _, c := range counts { + for _, c := range appSessionCounts { metricExported.SetAppSessions(c.Application, c.Status, c.Count) } } - if counts, err := store.CountChannelsByStatus(); err != nil { - logger.Error("failed to count channels", "error", err) + tvlCounts, err := store.GetTotalValueLocked() + if err != nil { + logger.Error("failed to get total value locked", "error", err) } else { - for _, c := range counts { - metricExported.SetChannels(c.Asset, c.Status, c.Count) + for _, c := range tvlCounts { + metricExported.SetTotalValueLocked(c.Domain, c.Asset, c.Value.InexactFloat64()) + } + } + + for _, tw := range timeSpans { + if counts, err := store.CountActiveUsers(tw.duration); err != nil { + logger.Error("failed to count active users", "timeframe", tw.label, "error", err) + } else { + for _, c := range counts { + metricExported.SetActiveUsers(c.Label, tw.label, c.Count) + } + } + } + + for _, tw := range timeSpans { + if counts, err := store.CountActiveAppSessions(tw.duration); err != nil { + logger.Error("failed to count active app sessions", "timeframe", tw.label, "error", err) + } else { + for _, c := range counts { + metricExported.SetActiveAppSessions(c.Label, tw.label, c.Count) + } } } + case <-ctx.Done(): logger.Info("stopping store metrics exporter") return diff --git a/clearnode/metrics/exporter.go b/clearnode/metrics/exporter.go index d6dd0e313..3148ae142 100644 --- a/clearnode/metrics/exporter.go +++ b/clearnode/metrics/exporter.go @@ -8,9 +8,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) const ( @@ -24,28 +24,49 @@ var ( ) type storeMetricExporter struct { - appSessionsTotal *prometheus.GaugeVec - channelsTotal *prometheus.GaugeVec + appSessionsTotal *prometheus.GaugeVec + channelsTotal *prometheus.GaugeVec + usersActive *prometheus.GaugeVec + appSessionsActive *prometheus.GaugeVec + totalValueLocked *prometheus.GaugeVec } func NewStoreMetricExporter(reg prometheus.Registerer) (StoreMetricExporter, error) { m := &storeMetricExporter{ appSessionsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricNamespace, - Name: "app_sessions_active", + Name: "app_sessions_total", Help: "Current total number of app sessions", }, []string{"application", "status"}), channelsTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: MetricNamespace, - Name: "channels_active", + Name: "channels_total", Help: "Current total number of channels", }, []string{"asset", "status"}), + usersActive: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "users_active", + Help: "Current total active users", + }, []string{"asset", "timespan"}), + appSessionsActive: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "app_sessions_active", + Help: "Current total active app sessions", + }, []string{"application", "timespan"}), + totalValueLocked: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: MetricNamespace, + Name: "total_value_locked", + Help: "Total value locked by domain and asset", + }, []string{"domain", "asset"}), } if reg != nil { reg.MustRegister( m.appSessionsTotal, m.channelsTotal, + m.usersActive, + m.appSessionsActive, + m.totalValueLocked, ) } else { return nil, fmt.Errorf("prometheus registerer not provided") @@ -62,6 +83,18 @@ func (m *storeMetricExporter) SetChannels(asset string, status core.ChannelStatu m.channelsTotal.WithLabelValues(asset, status.String()).Set(float64(count)) } +func (m *storeMetricExporter) SetActiveUsers(asset, timeSpanLabel string, count uint64) { + m.usersActive.WithLabelValues(asset, timeSpanLabel).Set(float64(count)) +} + +func (m *storeMetricExporter) SetActiveAppSessions(applicationID, timeSpanLabel string, count uint64) { + m.appSessionsActive.WithLabelValues(applicationID, timeSpanLabel).Set(float64(count)) +} + +func (m *storeMetricExporter) SetTotalValueLocked(domain, asset string, value float64) { + m.totalValueLocked.WithLabelValues(domain, asset).Set(value) +} + // runtimeMetricExporter is the concrete implementation of the Metrics interface. type runtimeMetricExporter struct { // Shared Metrics (Cross-Package) diff --git a/clearnode/metrics/interface.go b/clearnode/metrics/interface.go index c103e8ba2..db0df3f06 100644 --- a/clearnode/metrics/interface.go +++ b/clearnode/metrics/interface.go @@ -5,9 +5,9 @@ import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // RuntimeMetricExporter defines the interface for recording runtime metrics across various components of the system. @@ -60,4 +60,7 @@ func (noopRuntimeMetricExporter) IncBlockchainEvent(uint64, bool) {} type StoreMetricExporter interface { SetAppSessions(applicationID string, status app.AppSessionStatus, count uint64) SetChannels(asset string, status core.ChannelStatus, count uint64) + SetActiveUsers(asset, timeSpanLabel string, count uint64) + SetActiveAppSessions(applicationID, timeSpanLabel string, count uint64) + SetTotalValueLocked(domain, asset string, value float64) } diff --git a/clearnode/runtime.go b/clearnode/runtime.go index d58ec8189..c65733b59 100644 --- a/clearnode/runtime.go +++ b/clearnode/runtime.go @@ -15,15 +15,16 @@ import ( "github.com/joho/godotenv" "github.com/prometheus/client_golang/prometheus" - "github.com/erc7824/nitrolite/clearnode/metrics" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/clearnode/store/memory" - "github.com/erc7824/nitrolite/pkg/blockchain/evm" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" - "github.com/erc7824/nitrolite/pkg/sign/kms/gcp" + "github.com/layer-3/nitrolite/clearnode/action_gateway" + "github.com/layer-3/nitrolite/clearnode/metrics" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/clearnode/store/memory" + "github.com/layer-3/nitrolite/pkg/blockchain/evm" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/sign/kms/gcp" ) //go:embed config/migrations/*/*.sql @@ -36,9 +37,12 @@ type Backbone struct { ChannelMinChallengeDuration uint32 BlockchainRPCs map[uint64]string ValidationLimits ValidationLimits + RateLimitPerSec float64 + RateLimitBurst float64 DbStore database.DatabaseStore MemoryStore memory.MemoryStore + ActionGateway *action_gateway.ActionGateway RpcNode rpc.Node StateSigner sign.Signer TxSigner sign.Signer @@ -66,12 +70,17 @@ type Config struct { SignerKey string `yaml:"signer_key" env:"CLEARNODE_SIGNER_KEY"` // required when signer_type=key GCPKMSKeyName string `yaml:"gcp_kms_key_name" env:"CLEARNODE_GCP_KMS_KEY_NAME"` // required when signer_type=gcp-kms ValidationLimits ValidationLimits `yaml:"validation_limits"` + RateLimitPerSec float64 `yaml:"rate_limit_per_sec" env:"CLEARNODE_RATE_LIMIT_PER_SEC" env-default:"10"` + RateLimitBurst float64 `yaml:"rate_limit_burst" env:"CLEARNODE_RATE_LIMIT_BURST" env-default:"20"` + WsProcessBufferSize int `yaml:"ws_process_buffer_size" env:"CLEARNODE_WS_PROCESS_BUFFER_SIZE" env-default:"64"` + WsWriteBufferSize int `yaml:"ws_write_buffer_size" env:"CLEARNODE_WS_WRITE_BUFFER_SIZE" env-default:"64"` } // ValidationLimits defines configurable upper bounds for dynamic-length request fields. type ValidationLimits struct { MaxParticipants int `yaml:"max_participants" env:"CLEARNODE_MAX_PARTICIPANTS" env-default:"32"` MaxSessionDataLen int `yaml:"max_session_data_len" env:"CLEARNODE_MAX_SESSION_DATA_LEN" env-default:"1024"` + MaxAppMetadataLen int `yaml:"max_app_metadata_len" env:"CLEARNODE_MAX_APP_METADATA_LEN" env-default:"1024"` MaxSessionKeyIDs int `yaml:"max_session_key_ids" env:"CLEARNODE_MAX_SESSION_KEY_IDS" env-default:"256"` MaxSignedUpdates int `yaml:"max_signed_updates" env:"CLEARNODE_MAX_SIGNED_UPDATES" env-default:"0"` } @@ -130,6 +139,15 @@ func InitBackbone() *Backbone { logger.Fatal("failed to load blockchains", "error", err) } + // ------------------------------------------------ + // Action Gateway + // ------------------------------------------------ + + actionGateway, err := action_gateway.NewActionGatewayFromYaml(configDirPath) + if err != nil { + logger.Fatal("failed to initialize action gateway", "error", err) + } + // ------------------------------------------------ // Signer // ------------------------------------------------ @@ -189,8 +207,10 @@ func InitBackbone() *Backbone { // ------------------------------------------------ rpcNode, err := rpc.NewWebsocketNode(rpc.WebsocketNodeConfig{ - Logger: logger, - ObserveConnections: runtimeMetrics.SetRPCConnections, + Logger: logger, + ObserveConnections: runtimeMetrics.SetRPCConnections, + WsConnProcessBufferSize: conf.WsProcessBufferSize, + WsConnWriteBufferSize: conf.WsWriteBufferSize, }) if err != nil { logger.Fatal("failed to initialize RPC node", "error", err) @@ -232,9 +252,12 @@ func InitBackbone() *Backbone { ChannelMinChallengeDuration: conf.ChannelMinChallengeDuration, BlockchainRPCs: blockchainRPCs, ValidationLimits: conf.ValidationLimits, + RateLimitPerSec: conf.RateLimitPerSec, + RateLimitBurst: conf.RateLimitBurst, DbStore: dbStore, MemoryStore: memoryStore, + ActionGateway: actionGateway, RpcNode: rpcNode, StateSigner: stateSigner, TxSigner: txSigner, diff --git a/clearnode/runtime_test.go b/clearnode/runtime_test.go index 0fd342827..2b66d9917 100644 --- a/clearnode/runtime_test.go +++ b/clearnode/runtime_test.go @@ -6,7 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) func TestCheckChannelHubVersion_Manual(t *testing.T) { diff --git a/clearnode/store/database/action_log.go b/clearnode/store/database/action_log.go new file mode 100644 index 000000000..d6e331cad --- /dev/null +++ b/clearnode/store/database/action_log.go @@ -0,0 +1,91 @@ +package database + +import ( + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "github.com/layer-3/nitrolite/pkg/core" +) + +type ActionLogEntryV1 struct { + ID uuid.UUID `gorm:"type:char(36);primaryKey"` + UserWallet string `gorm:"column:user_wallet;not null"` + GatedAction uint8 `gorm:"column:gated_action;not null"` + CreatedAt time.Time +} + +func (ActionLogEntryV1) TableName() string { + return "action_log_v1" +} + +// RecordAction inserts a new action log entry for a user. +func (s *DBStore) RecordAction(wallet string, gatedAction core.GatedAction) error { + if gatedAction.ID() == 0 { + return fmt.Errorf("invalid gated action ID") + } + + wallet = strings.ToLower(wallet) + + entry := ActionLogEntryV1{ + ID: uuid.New(), + UserWallet: wallet, + GatedAction: gatedAction.ID(), + CreatedAt: time.Now(), + } + + if err := s.db.Create(&entry).Error; err != nil { + return fmt.Errorf("failed to record action log entry: %w", err) + } + + return nil +} + +// GetUserActionCount returns the number of actions matching the given wallet and gated action +// within the specified time window (counting backwards from now). +func (s *DBStore) GetUserActionCount(wallet string, gatedAction core.GatedAction, window time.Duration) (uint64, error) { + wallet = strings.ToLower(wallet) + since := time.Now().Add(-window) + + query := s.db.Model(&ActionLogEntryV1{}). + Where("user_wallet = ? AND gated_action = ? AND created_at >= ?", wallet, gatedAction.ID(), since) + + var count int64 + if err := query.Count(&count).Error; err != nil { + return 0, fmt.Errorf("failed to get user action count: %w", err) + } + + return uint64(count), nil +} + +func (s *DBStore) GetUserActionCounts(userWallet string, window time.Duration) (map[core.GatedAction]uint64, error) { + userWallet = strings.ToLower(userWallet) + since := time.Now().Add(-window) + + query := s.db.Model(&ActionLogEntryV1{}). + Select("gated_action, COUNT(id) as count"). + Where("user_wallet = ? AND created_at >= ?", userWallet, since). + Group("gated_action") + + type Result struct { + GatedAction uint8 + Count int64 + } + + var results []Result + if err := query.Scan(&results).Error; err != nil { + return nil, fmt.Errorf("failed to get user action counts: %w", err) + } + + counts := make(map[core.GatedAction]uint64) + for _, r := range results { + action, ok := core.GatedActionFromID(r.GatedAction) + if !ok { + continue + } + counts[action] = uint64(r.Count) + } + + return counts, nil +} diff --git a/clearnode/store/database/action_log_test.go b/clearnode/store/database/action_log_test.go new file mode 100644 index 000000000..a0a5cf262 --- /dev/null +++ b/clearnode/store/database/action_log_test.go @@ -0,0 +1,249 @@ +package database + +import ( + "testing" + "time" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRecordAction(t *testing.T) { + t.Run("records action successfully", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + err := store.RecordAction("0xUser123", core.GatedActionTransfer) + require.NoError(t, err) + + count, err := store.GetUserActionCount("0xuser123", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + }) + + t.Run("records multiple actions", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + + count, err := store.GetUserActionCount("0xuser", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(3), count) + }) + + t.Run("normalizes wallet to lowercase", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xABCDEF", core.GatedActionTransfer)) + + count, err := store.GetUserActionCount("0xabcdef", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + }) +} + +func TestGetUserActionCount(t *testing.T) { + t.Run("returns zero for no actions", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + count, err := store.GetUserActionCount("0xuser", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(0), count) + }) + + t.Run("filters by gated action", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionAppSessionOperation)) + + transferCount, err := store.GetUserActionCount("0xuser", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(2), transferCount) + + opCount, err := store.GetUserActionCount("0xuser", core.GatedActionAppSessionOperation, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), opCount) + }) + + t.Run("filters by wallet", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xuser1", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser1", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser2", core.GatedActionTransfer)) + + count, err := store.GetUserActionCount("0xuser1", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(2), count) + + count, err = store.GetUserActionCount("0xuser2", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + }) + + t.Run("respects time window", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + oldEntry := ActionLogEntryV1{ + ID: [16]byte{1}, + UserWallet: "0xuser", + GatedAction: core.GatedActionTransfer.ID(), + CreatedAt: time.Now().Add(-2 * time.Hour), + } + require.NoError(t, db.Create(&oldEntry).Error) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + + count, err := store.GetUserActionCount("0xuser", core.GatedActionTransfer, time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + + count, err = store.GetUserActionCount("0xuser", core.GatedActionTransfer, 3*time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(2), count) + }) +} + +func TestGetUserActionCounts(t *testing.T) { + t.Run("returns empty map for no actions", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + counts, err := store.GetUserActionCounts("0xuser", time.Hour) + require.NoError(t, err) + assert.Empty(t, counts) + }) + + t.Run("groups by gated action", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionAppSessionOperation)) + + counts, err := store.GetUserActionCounts("0xuser", time.Hour) + require.NoError(t, err) + + assert.Equal(t, uint64(2), counts[core.GatedActionTransfer]) + assert.Equal(t, uint64(1), counts[core.GatedActionAppSessionOperation]) + }) + + t.Run("filters by wallet", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, store.RecordAction("0xuser1", core.GatedActionTransfer)) + require.NoError(t, store.RecordAction("0xuser2", core.GatedActionTransfer)) + + counts, err := store.GetUserActionCounts("0xuser1", time.Hour) + require.NoError(t, err) + assert.Len(t, counts, 1) + }) + + t.Run("respects time window", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + oldEntry := ActionLogEntryV1{ + ID: [16]byte{1}, + UserWallet: "0xuser", + GatedAction: core.GatedActionTransfer.ID(), + CreatedAt: time.Now().Add(-2 * time.Hour), + } + require.NoError(t, db.Create(&oldEntry).Error) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + + counts, err := store.GetUserActionCounts("0xuser", time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(1), counts[core.GatedActionTransfer]) + + counts, err = store.GetUserActionCounts("0xuser", 3*time.Hour) + require.NoError(t, err) + assert.Equal(t, uint64(2), counts[core.GatedActionTransfer]) + }) + + t.Run("skips unknown gated action IDs", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + // Insert an entry with an unknown gated action ID directly + entry := ActionLogEntryV1{ + ID: [16]byte{99}, + UserWallet: "0xuser", + GatedAction: 255, // unknown ID + CreatedAt: time.Now(), + } + require.NoError(t, db.Create(&entry).Error) + require.NoError(t, store.RecordAction("0xuser", core.GatedActionTransfer)) + + counts, err := store.GetUserActionCounts("0xuser", time.Hour) + require.NoError(t, err) + assert.Len(t, counts, 1) + assert.Equal(t, uint64(1), counts[core.GatedActionTransfer]) + }) +} + +func TestGetAppCount(t *testing.T) { + t.Run("returns zero for no apps", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + count, err := store.GetAppCount("0xowner") + require.NoError(t, err) + assert.Equal(t, uint64(0), count) + }) + + t.Run("counts apps for owner", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, db.Create(&AppV1{ID: "app1", OwnerWallet: "0xowner1", Metadata: "{}"}).Error) + require.NoError(t, db.Create(&AppV1{ID: "app2", OwnerWallet: "0xowner1", Metadata: "{}"}).Error) + require.NoError(t, db.Create(&AppV1{ID: "app3", OwnerWallet: "0xowner2", Metadata: "{}"}).Error) + + count, err := store.GetAppCount("0xowner1") + require.NoError(t, err) + assert.Equal(t, uint64(2), count) + + count, err = store.GetAppCount("0xowner2") + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + }) + + t.Run("normalizes wallet to lowercase", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := NewDBStore(db) + + require.NoError(t, db.Create(&AppV1{ID: "app1", OwnerWallet: "0xabcdef", Metadata: "{}"}).Error) + + count, err := store.GetAppCount("0xABCDEF") + require.NoError(t, err) + assert.Equal(t, uint64(1), count) + }) +} diff --git a/clearnode/store/database/app.go b/clearnode/store/database/app.go new file mode 100644 index 000000000..5df3757f8 --- /dev/null +++ b/clearnode/store/database/app.go @@ -0,0 +1,118 @@ +package database + +import ( + "fmt" + "strings" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "gorm.io/gorm" +) + +// AppV1 represents an application registry entry in the database. +type AppV1 struct { + ID string `gorm:"primaryKey"` + OwnerWallet string `gorm:"column:owner_wallet;not null"` + Metadata string `gorm:"column:metadata;type:text;not null"` + Version uint64 `gorm:"column:version;default:1"` + CreationApprovalNotRequired bool `gorm:"column:creation_approval_not_required"` + CreatedAt time.Time + UpdatedAt time.Time +} + +func (AppV1) TableName() string { + return "apps_v1" +} + +// CreateApp registers a new application. Returns an error if the app ID already exists. +func (s *DBStore) CreateApp(entry app.AppV1) error { + dbApp := AppV1{ + ID: strings.ToLower(entry.ID), + OwnerWallet: strings.ToLower(entry.OwnerWallet), + Metadata: entry.Metadata, + Version: entry.Version, + CreationApprovalNotRequired: entry.CreationApprovalNotRequired, + } + + if err := s.db.Create(&dbApp).Error; err != nil { + return fmt.Errorf("failed to create app: %w", err) + } + + return nil +} + +// GetApp retrieves a single application by ID. Returns nil if not found. +func (s *DBStore) GetApp(appID string) (*app.AppInfoV1, error) { + var dbApp AppV1 + err := s.db.Where("id = ?", strings.ToLower(appID)).First(&dbApp).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to get app: %w", err) + } + + result := databaseAppToCore(&dbApp) + return &result, nil +} + +// GetApps retrieves applications with optional filtering by app ID, owner wallet, and pagination. +func (s *DBStore) GetApps(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) { + query := s.db.Model(&AppV1{}) + + if appID != nil && *appID != "" { + query = query.Where("id = ?", strings.ToLower(*appID)) + } + + if ownerWallet != nil && *ownerWallet != "" { + query = query.Where("owner_wallet = ?", strings.ToLower(*ownerWallet)) + } + + var totalCount int64 + if err := query.Count(&totalCount).Error; err != nil { + return nil, core.PaginationMetadata{}, fmt.Errorf("failed to count apps: %w", err) + } + + offset, limit := pagination.GetOffsetAndLimit(DefaultLimit, MaxLimit) + + query = query.Order("created_at DESC").Offset(int(offset)).Limit(int(limit)) + + var dbApps []AppV1 + if err := query.Find(&dbApps).Error; err != nil { + return nil, core.PaginationMetadata{}, fmt.Errorf("failed to get apps: %w", err) + } + + apps := make([]app.AppInfoV1, len(dbApps)) + for i, dbApp := range dbApps { + apps[i] = databaseAppToCore(&dbApp) + } + + metadata := calculatePaginationMetadata(totalCount, offset, limit) + + return apps, metadata, nil +} + +func databaseAppToCore(dbApp *AppV1) app.AppInfoV1 { + return app.AppInfoV1{ + App: app.AppV1{ + ID: dbApp.ID, + OwnerWallet: dbApp.OwnerWallet, + Metadata: dbApp.Metadata, + Version: dbApp.Version, + CreationApprovalNotRequired: dbApp.CreationApprovalNotRequired, + }, + CreatedAt: dbApp.CreatedAt, + UpdatedAt: dbApp.UpdatedAt, + } +} + +func (s *DBStore) GetAppCount(ownerWallet string) (uint64, error) { + var count int64 + err := s.db.Model(&AppV1{}).Where("owner_wallet = ?", strings.ToLower(ownerWallet)).Count(&count).Error + if err != nil { + return 0, fmt.Errorf("failed to count apps: %w", err) + } + + return uint64(count), nil +} diff --git a/clearnode/store/database/app_ledger_test.go b/clearnode/store/database/app_ledger_test.go index ac4cc7fca..afc318d7d 100644 --- a/clearnode/store/database/app_ledger_test.go +++ b/clearnode/store/database/app_ledger_test.go @@ -3,7 +3,7 @@ package database import ( "testing" - "github.com/erc7824/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/app" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -204,9 +204,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create app session with participant session := app.AppSessionV1{ - SessionID: "session123", - Application: "poker", - Nonce: 1, + SessionID: "session123", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -242,9 +242,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create app session with participants session := app.AppSessionV1{ - SessionID: "session456", - Application: "poker", - Nonce: 1, + SessionID: "session456", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -286,9 +286,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create app session with participant session := app.AppSessionV1{ - SessionID: "session789", - Application: "poker", - Nonce: 1, + SessionID: "session789", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -326,9 +326,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create app session with participant but no ledger entries session := app.AppSessionV1{ - SessionID: "session100", - Application: "poker", - Nonce: 1, + SessionID: "session100", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -366,9 +366,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create app session with one participant session := app.AppSessionV1{ - SessionID: "session200", - Application: "poker", - Nonce: 1, + SessionID: "session200", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -406,9 +406,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { // Create two app sessions with the same participant session1 := app.AppSessionV1{ - SessionID: "session300", - Application: "poker", - Nonce: 1, + SessionID: "session300", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -423,9 +423,9 @@ func TestDBStore_GetParticipantAllocations(t *testing.T) { require.NoError(t, store.CreateAppSession(session1)) session2 := app.AppSessionV1{ - SessionID: "session400", - Application: "poker", - Nonce: 2, + SessionID: "session400", + ApplicationID: "poker", + Nonce: 2, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", diff --git a/clearnode/store/database/app_session.go b/clearnode/store/database/app_session.go index ef93afd9f..14c946cf4 100644 --- a/clearnode/store/database/app_session.go +++ b/clearnode/store/database/app_session.go @@ -5,23 +5,23 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" "gorm.io/gorm" ) // AppSessionV1 represents a virtual payment application session between participants type AppSessionV1 struct { - ID string `gorm:"primaryKey"` - Application string `gorm:"column:application;not null"` - Nonce uint64 `gorm:"column:nonce;not null"` - Participants []AppParticipantV1 `gorm:"foreignKey:AppSessionID;references:ID"` - SessionData string `gorm:"column:session_data;type:text;not null"` - Quorum uint8 `gorm:"column:quorum;default:100"` - Version uint64 `gorm:"column:version;default:1"` - Status app.AppSessionStatus `gorm:"column:status;not null"` - CreatedAt time.Time - UpdatedAt time.Time + ID string `gorm:"primaryKey"` + ApplicationID string `gorm:"column:application_id;not null"` + Nonce uint64 `gorm:"column:nonce;not null"` + Participants []AppParticipantV1 `gorm:"foreignKey:AppSessionID;references:ID"` + SessionData string `gorm:"column:session_data;type:text;not null"` + Quorum uint8 `gorm:"column:quorum;default:100"` + Version uint64 `gorm:"column:version;default:1"` + Status app.AppSessionStatus `gorm:"column:status;not null"` + CreatedAt time.Time + UpdatedAt time.Time } func (AppSessionV1) TableName() string { @@ -51,16 +51,16 @@ func (s *DBStore) CreateAppSession(session app.AppSessionV1) error { } dbSession := AppSessionV1{ - ID: strings.ToLower(session.SessionID), - Application: session.Application, - Nonce: session.Nonce, - Participants: participants, - SessionData: session.SessionData, - Quorum: session.Quorum, - Version: session.Version, - Status: session.Status, - CreatedAt: session.CreatedAt, - UpdatedAt: session.UpdatedAt, + ID: strings.ToLower(session.SessionID), + ApplicationID: strings.ToLower(session.ApplicationID), + Nonce: session.Nonce, + Participants: participants, + SessionData: session.SessionData, + Quorum: session.Quorum, + Version: session.Version, + Status: session.Status, + CreatedAt: session.CreatedAt, + UpdatedAt: session.UpdatedAt, } if err := s.db.Create(&dbSession).Error; err != nil { @@ -133,26 +133,6 @@ func (s *DBStore) GetAppSessions(appSessionID *string, participant *string, stat return sessions, metadata, nil } -// AppSessionCount holds the result of a COUNT() GROUP BY query on app sessions. -type AppSessionCount struct { - Application string `gorm:"column:application"` - Status app.AppSessionStatus `gorm:"column:status"` - Count uint64 `gorm:"column:count"` -} - -// CountAppSessionsByStatus returns app session counts grouped by (application, status). -func (s *DBStore) CountAppSessionsByStatus() ([]AppSessionCount, error) { - var results []AppSessionCount - err := s.db.Model(&AppSessionV1{}). - Select("application, status, COUNT(id) as count"). - Group("application, status"). - Find(&results).Error - if err != nil { - return nil, fmt.Errorf("failed to count app sessions: %w", err) - } - return results, nil -} - // UpdateAppSession updates existing session data with optimistic locking. func (s *DBStore) UpdateAppSession(session app.AppSessionV1) error { return s.db.Transaction(func(tx *gorm.DB) error { diff --git a/clearnode/store/database/app_session_key_state.go b/clearnode/store/database/app_session_key_state.go index 2733f2fc9..76bcdd597 100644 --- a/clearnode/store/database/app_session_key_state.go +++ b/clearnode/store/database/app_session_key_state.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/app" "gorm.io/gorm" ) @@ -194,7 +194,7 @@ func (s *DBStore) GetAppSessionKeyOwner(sessionKey, appSessionId string) (string appSessionId = strings.ToLower(appSessionId) // Subquery to get the application ID from the app session - appSubQuery := s.db.Model(&AppSessionV1{}).Select("application").Where("id = ?", appSessionId) + appSubQuery := s.db.Model(&AppSessionV1{}).Select("application_id").Where("id = ?", appSessionId) maxVersionSubQ := s.db.Model(&AppSessionKeyStateV1{}). Select("MAX(version)"). diff --git a/clearnode/store/database/app_session_key_state_test.go b/clearnode/store/database/app_session_key_state_test.go index 2818e1507..823f76049 100644 --- a/clearnode/store/database/app_session_key_state_test.go +++ b/clearnode/store/database/app_session_key_state_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/app" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -852,9 +852,9 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { // Create an app session that the session key references appSession := app.AppSessionV1{ - SessionID: testSess1, - Application: "poker", - Nonce: 1, + SessionID: testSess1, + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ {WalletAddress: testUser1, SignatureWeight: 100}, }, @@ -891,9 +891,9 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { // Create an app session with a specific application appSession := app.AppSessionV1{ - SessionID: testSess1, - Application: testApp1, - Nonce: 1, + SessionID: testSess1, + ApplicationID: testApp1, + Nonce: 1, Participants: []app.AppParticipantV1{ {WalletAddress: testUser1, SignatureWeight: 100}, }, @@ -941,9 +941,9 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { // Create an app session appSession := app.AppSessionV1{ - SessionID: testSess1, - Application: "poker", - Nonce: 1, + SessionID: testSess1, + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ {WalletAddress: testUser1, SignatureWeight: 100}, }, @@ -980,9 +980,9 @@ func TestDBStore_GetAppSessionKeyOwner(t *testing.T) { // Create an app session appSession := app.AppSessionV1{ - SessionID: testSess1, - Application: "poker", - Nonce: 1, + SessionID: testSess1, + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ {WalletAddress: testUser1, SignatureWeight: 100}, }, diff --git a/clearnode/store/database/app_session_test.go b/clearnode/store/database/app_session_test.go index c0b9437d7..0d8746b92 100644 --- a/clearnode/store/database/app_session_test.go +++ b/clearnode/store/database/app_session_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,9 +28,9 @@ func TestDBStore_CreateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session123", - Application: "poker", - Nonce: 1, + SessionID: "session123", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -54,7 +54,7 @@ func TestDBStore_CreateAppSession(t *testing.T) { require.NoError(t, err) assert.Equal(t, "session123", dbSession.ID) - assert.Equal(t, "poker", dbSession.Application) + assert.Equal(t, "poker", dbSession.ApplicationID) assert.Equal(t, uint64(1), dbSession.Nonce) assert.Equal(t, `{"state": "active"}`, dbSession.SessionData) assert.Equal(t, uint8(100), dbSession.Quorum) @@ -72,9 +72,9 @@ func TestDBStore_CreateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session456", - Application: "chess", - Nonce: 1, + SessionID: "session456", + ApplicationID: "chess", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -118,9 +118,9 @@ func TestDBStore_CreateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session789", - Application: "poker", - Nonce: 1, + SessionID: "session789", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -152,9 +152,9 @@ func TestDBStore_GetAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session123", - Application: "poker", - Nonce: 1, + SessionID: "session123", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -176,7 +176,7 @@ func TestDBStore_GetAppSession(t *testing.T) { require.NotNil(t, result) assert.Equal(t, "session123", result.SessionID) - assert.Equal(t, "poker", result.Application) + assert.Equal(t, "poker", result.ApplicationID) assert.Equal(t, uint64(1), result.Nonce) assert.Equal(t, `{"state": "active"}`, result.SessionData) assert.Equal(t, uint8(100), result.Quorum) @@ -207,9 +207,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { // Create multiple sessions session1 := app.AppSessionV1{ - SessionID: "session1", - Application: "poker", - Nonce: 1, + SessionID: "session1", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -225,9 +225,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { } session2 := app.AppSessionV1{ - SessionID: "session2", - Application: "chess", - Nonce: 1, + SessionID: "session2", + ApplicationID: "chess", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser456", @@ -264,9 +264,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { store := NewDBStore(db) session1 := app.AppSessionV1{ - SessionID: "session1", - Application: "poker", - Nonce: 1, + SessionID: "session1", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -282,9 +282,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { } session2 := app.AppSessionV1{ - SessionID: "session2", - Application: "chess", - Nonce: 1, + SessionID: "session2", + ApplicationID: "chess", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser456", @@ -319,9 +319,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { store := NewDBStore(db) session1 := app.AppSessionV1{ - SessionID: "session1", - Application: "poker", - Nonce: 1, + SessionID: "session1", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -337,9 +337,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { } session2 := app.AppSessionV1{ - SessionID: "session2", - Application: "chess", - Nonce: 1, + SessionID: "session2", + ApplicationID: "chess", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser456", @@ -374,9 +374,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { store := NewDBStore(db) session1 := app.AppSessionV1{ - SessionID: "session1", - Application: "poker", - Nonce: 1, + SessionID: "session1", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -392,9 +392,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { } session2 := app.AppSessionV1{ - SessionID: "session2", - Application: "chess", - Nonce: 1, + SessionID: "session2", + ApplicationID: "chess", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser456", @@ -432,9 +432,9 @@ func TestDBStore_GetAppSessions(t *testing.T) { for i := 1; i <= 3; i++ { sessionID := "session" + string(rune(i+'0')) session := app.AppSessionV1{ - SessionID: sessionID, - Application: "poker", - Nonce: uint64(i), + SessionID: sessionID, + ApplicationID: "poker", + Nonce: uint64(i), Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -500,9 +500,9 @@ func TestDBStore_UpdateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session123", - Application: "poker", - Nonce: 1, + SessionID: "session123", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -544,9 +544,9 @@ func TestDBStore_UpdateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "nonexistent", - Application: "poker", - Nonce: 1, + SessionID: "nonexistent", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", @@ -573,9 +573,9 @@ func TestDBStore_UpdateAppSession(t *testing.T) { store := NewDBStore(db) session := app.AppSessionV1{ - SessionID: "session456", - Application: "poker", - Nonce: 1, + SessionID: "session456", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0xuser123", diff --git a/clearnode/store/database/app_test.go b/clearnode/store/database/app_test.go new file mode 100644 index 000000000..2293ddada --- /dev/null +++ b/clearnode/store/database/app_test.go @@ -0,0 +1,317 @@ +package database + +import ( + "testing" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAppV1_TableName(t *testing.T) { + a := AppV1{} + assert.Equal(t, "apps_v1", a.TableName()) +} + +func TestDBStore_CreateApp(t *testing.T) { + t.Run("Success", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + entry := app.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0xabcdef", + Version: 1, + CreationApprovalNotRequired: true, + } + + err := store.CreateApp(entry) + require.NoError(t, err) + + // Verify app was created + var dbApp AppV1 + err = db.Where("id = ?", "test-app").First(&dbApp).Error + require.NoError(t, err) + + assert.Equal(t, "test-app", dbApp.ID) + assert.Equal(t, "0x1111111111111111111111111111111111111111", dbApp.OwnerWallet) + assert.Equal(t, "0xabcdef", dbApp.Metadata) + assert.Equal(t, uint64(1), dbApp.Version) + assert.True(t, dbApp.CreationApprovalNotRequired) + }) + + t.Run("Duplicate ID error", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + entry := app.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0xabcdef", + Version: 1, + } + + err := store.CreateApp(entry) + require.NoError(t, err) + + // Try to create again with same ID + err = store.CreateApp(entry) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to create app") + }) + + t.Run("Stores lowercase ID and wallet", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + entry := app.AppV1{ + ID: "My-App", + OwnerWallet: "0xABCD1234567890ABCDEF1234567890ABCDEF1234", + Metadata: "0x00", + Version: 1, + } + + err := store.CreateApp(entry) + require.NoError(t, err) + + var dbApp AppV1 + err = db.Where("id = ?", "my-app").First(&dbApp).Error + require.NoError(t, err) + + assert.Equal(t, "my-app", dbApp.ID) + assert.Equal(t, "0xabcd1234567890abcdef1234567890abcdef1234", dbApp.OwnerWallet) + }) +} + +func TestDBStore_GetApp(t *testing.T) { + t.Run("Found", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + entry := app.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0xabcdef", + Version: 1, + CreationApprovalNotRequired: true, + } + require.NoError(t, store.CreateApp(entry)) + + result, err := store.GetApp("test-app") + require.NoError(t, err) + require.NotNil(t, result) + + assert.Equal(t, "test-app", result.App.ID) + assert.Equal(t, "0x1111111111111111111111111111111111111111", result.App.OwnerWallet) + assert.Equal(t, "0xabcdef", result.App.Metadata) + assert.Equal(t, uint64(1), result.App.Version) + assert.True(t, result.App.CreationApprovalNotRequired) + }) + + t.Run("Not found returns nil", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + result, err := store.GetApp("nonexistent") + require.NoError(t, err) + assert.Nil(t, result) + }) + + t.Run("Case-insensitive lookup", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + entry := app.AppV1{ + ID: "test-app", + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x00", + Version: 1, + } + require.NoError(t, store.CreateApp(entry)) + + // Look up with different casing + result, err := store.GetApp("Test-App") + require.NoError(t, err) + require.NotNil(t, result) + assert.Equal(t, "test-app", result.App.ID) + }) +} + +func TestDBStore_GetApps(t *testing.T) { + t.Run("No filter", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-1", OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x01", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-2", OwnerWallet: "0x2222222222222222222222222222222222222222", + Metadata: "0x02", Version: 1, + })) + + // Small delay to ensure different created_at times + time.Sleep(10 * time.Millisecond) + + pagination := &core.PaginationParams{} + apps, metadata, err := store.GetApps(nil, nil, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 2) + assert.Equal(t, uint32(2), metadata.TotalCount) + }) + + t.Run("Filter by appID", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-1", OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x01", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-2", OwnerWallet: "0x2222222222222222222222222222222222222222", + Metadata: "0x02", Version: 1, + })) + + appID := "app-1" + pagination := &core.PaginationParams{} + apps, metadata, err := store.GetApps(&appID, nil, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 1) + assert.Equal(t, uint32(1), metadata.TotalCount) + assert.Equal(t, "app-1", apps[0].App.ID) + }) + + t.Run("Filter by ownerWallet", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + owner := "0x1111111111111111111111111111111111111111" + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-1", OwnerWallet: owner, Metadata: "0x01", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-2", OwnerWallet: "0x2222222222222222222222222222222222222222", + Metadata: "0x02", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-3", OwnerWallet: owner, Metadata: "0x03", Version: 1, + })) + + pagination := &core.PaginationParams{} + apps, metadata, err := store.GetApps(nil, &owner, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 2) + assert.Equal(t, uint32(2), metadata.TotalCount) + for _, a := range apps { + assert.Equal(t, owner, a.App.OwnerWallet) + } + }) + + t.Run("Combined filters", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + owner := "0x1111111111111111111111111111111111111111" + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-1", OwnerWallet: owner, Metadata: "0x01", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-2", OwnerWallet: owner, Metadata: "0x02", Version: 1, + })) + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-3", OwnerWallet: "0x2222222222222222222222222222222222222222", + Metadata: "0x03", Version: 1, + })) + + appID := "app-1" + pagination := &core.PaginationParams{} + apps, metadata, err := store.GetApps(&appID, &owner, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 1) + assert.Equal(t, uint32(1), metadata.TotalCount) + assert.Equal(t, "app-1", apps[0].App.ID) + assert.Equal(t, owner, apps[0].App.OwnerWallet) + }) + + t.Run("Pagination", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + for i := 1; i <= 3; i++ { + require.NoError(t, store.CreateApp(app.AppV1{ + ID: "app-" + string(rune(i+'0')), + OwnerWallet: "0x1111111111111111111111111111111111111111", + Metadata: "0x00", + Version: 1, + })) + time.Sleep(10 * time.Millisecond) + } + + limit := uint32(2) + offset := uint32(0) + pagination := &core.PaginationParams{Limit: &limit, Offset: &offset} + + apps, metadata, err := store.GetApps(nil, nil, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 2) + assert.Equal(t, uint32(3), metadata.TotalCount) + assert.Equal(t, uint32(1), metadata.Page) + assert.Equal(t, uint32(2), metadata.PerPage) + + // Second page + offset = 2 + pagination.Offset = &offset + apps, metadata, err = store.GetApps(nil, nil, pagination) + require.NoError(t, err) + + assert.Len(t, apps, 1) + assert.Equal(t, uint32(2), metadata.Page) + }) + + t.Run("Empty results", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + + store := NewDBStore(db) + + appID := "nonexistent" + pagination := &core.PaginationParams{} + apps, metadata, err := store.GetApps(&appID, nil, pagination) + require.NoError(t, err) + + assert.Empty(t, apps) + assert.Equal(t, uint32(0), metadata.TotalCount) + }) +} diff --git a/clearnode/store/database/blockchain_action_test.go b/clearnode/store/database/blockchain_action_test.go index a1a09cff1..c94e24be7 100644 --- a/clearnode/store/database/blockchain_action_test.go +++ b/clearnode/store/database/blockchain_action_test.go @@ -3,8 +3,8 @@ package database import ( "testing" - "github.com/erc7824/nitrolite/pkg/core" "github.com/ethereum/go-ethereum/common" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/clearnode/store/database/channel.go b/clearnode/store/database/channel.go index a407ea7c8..3979fa95a 100644 --- a/clearnode/store/database/channel.go +++ b/clearnode/store/database/channel.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "gorm.io/gorm" ) @@ -152,27 +152,6 @@ func (s *DBStore) GetUserChannels(wallet string, status *core.ChannelStatus, ass return channels, uint32(totalCount), nil } -// ChannelCount holds the result of a COUNT() GROUP BY query on channels. -type ChannelCount struct { - Asset string `gorm:"column:asset"` - Status core.ChannelStatus `gorm:"column:status"` - Count uint64 `gorm:"column:count"` -} - -// CountChannelsByStatus returns channel counts grouped by (asset, status). -func (s *DBStore) CountChannelsByStatus() ([]ChannelCount, error) { - var results []ChannelCount - err := s.db.Raw(` - SELECT asset, status, COUNT(*) as count - FROM channels - GROUP BY asset, status - `).Scan(&results).Error - if err != nil { - return nil, fmt.Errorf("failed to count channels: %w", err) - } - return results, nil -} - // UpdateChannel persists changes to a channel's metadata (status, version, etc). func (s *DBStore) UpdateChannel(channel core.Channel) error { updates := map[string]interface{}{ diff --git a/clearnode/store/database/channel_session_key_state.go b/clearnode/store/database/channel_session_key_state.go index 6eb23e179..aa41159a7 100644 --- a/clearnode/store/database/channel_session_key_state.go +++ b/clearnode/store/database/channel_session_key_state.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "gorm.io/gorm" ) diff --git a/clearnode/store/database/channel_session_key_state_test.go b/clearnode/store/database/channel_session_key_state_test.go index a6f670105..4263cc63d 100644 --- a/clearnode/store/database/channel_session_key_state_test.go +++ b/clearnode/store/database/channel_session_key_state_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/clearnode/store/database/channel_test.go b/clearnode/store/database/channel_test.go index 2e13dbe6d..0be5d2559 100644 --- a/clearnode/store/database/channel_test.go +++ b/clearnode/store/database/channel_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/clearnode/store/database/contract_event.go b/clearnode/store/database/contract_event.go index b11465ece..955feb58d 100644 --- a/clearnode/store/database/contract_event.go +++ b/clearnode/store/database/contract_event.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "gorm.io/gorm" ) diff --git a/clearnode/store/database/contract_event_test.go b/clearnode/store/database/contract_event_test.go index 090dd17f4..72e054d30 100644 --- a/clearnode/store/database/contract_event_test.go +++ b/clearnode/store/database/contract_event_test.go @@ -3,7 +3,7 @@ package database import ( "testing" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/clearnode/store/database/database.go b/clearnode/store/database/database.go index 4ba74d4d6..bb01c3189 100644 --- a/clearnode/store/database/database.go +++ b/clearnode/store/database/database.go @@ -67,6 +67,7 @@ func connectToPostgresql(cnf DatabaseConfig, embedMigrations embed.FS) (*gorm.DB } dial := postgres.Open(dsn) + // TODO: don't print SQL in logs db, err := gorm.Open(dial, &gorm.Config{ NamingStrategy: schema.NamingStrategy{ TablePrefix: cnf.Schema + ".", // schema name @@ -213,7 +214,7 @@ func migratePostgres(cnf DatabaseConfig, embedMigrations embed.FS) error { } func migrateSqlite(db *gorm.DB) error { - if err := db.AutoMigrate(&AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &UserBalance{}); err != nil { + if err := db.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}); err != nil { return err } return nil diff --git a/clearnode/store/database/db_store.go b/clearnode/store/database/db_store.go index 21fe52a91..c87ae2331 100644 --- a/clearnode/store/database/db_store.go +++ b/clearnode/store/database/db_store.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/clearnode/store/database/db_store_test.go b/clearnode/store/database/db_store_test.go index 3a819ded0..e5b113d10 100644 --- a/clearnode/store/database/db_store_test.go +++ b/clearnode/store/database/db_store_test.go @@ -3,7 +3,7 @@ package database import ( "testing" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/clearnode/store/database/interface.go b/clearnode/store/database/interface.go index d6b5fc7b8..06bb4cbba 100644 --- a/clearnode/store/database/interface.go +++ b/clearnode/store/database/interface.go @@ -1,8 +1,10 @@ package database import ( - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) @@ -113,6 +115,20 @@ type DatabaseStore interface { // GetStateByID retrieves a state by its deterministic ID. GetStateByID(stateID string) (*core.State, error) + // --- App Registry Operations --- + + // CreateApp registers a new application. Returns an error if the app ID already exists. + CreateApp(entry app.AppV1) error + + // GetApp retrieves a single application by ID. Returns nil if not found. + GetApp(appID string) (*app.AppInfoV1, error) + + // GetApps retrieves applications with optional filtering by app ID, owner wallet, and pagination. + GetApps(appID *string, ownerWallet *string, pagination *core.PaginationParams) ([]app.AppInfoV1, core.PaginationMetadata, error) + + // GetAppCount returns the total number of applications owned by a specific wallet. + GetAppCount(ownerWallet string) (uint64, error) + // --- App Session Operations --- // CreateAppSession initializes a new application session. @@ -176,11 +192,45 @@ type DatabaseStore interface { // --- Metric Aggregation --- - // CountAppSessionsByStatus returns app session counts grouped by (application, status). - CountAppSessionsByStatus() ([]AppSessionCount, error) + // CountActiveUsers returns distinct user counts per asset within the given window. + CountActiveUsers(window time.Duration) ([]ActiveCountByLabel, error) + + // CountActiveAppSessions returns app session counts per application within the given window. + CountActiveAppSessions(window time.Duration) ([]ActiveCountByLabel, error) + + // --- Lifespan Metric Operations --- + + // GetLifetimeMetricLastTimestamp returns the most recent last_timestamp among all metrics with the given name. + GetLifetimeMetricLastTimestamp(name string) (time.Time, error) + + // GetAppSessionsCountByLabels computes app session count deltas, upserts as lifespan metrics, and returns updated totals. + GetAppSessionsCountByLabels() ([]AppSessionCount, error) + + // GetChannelsCountByLabels computes channel count deltas, upserts as lifespan metrics, and returns updated totals. + GetChannelsCountByLabels() ([]ChannelCount, error) + + // GetTotalValueLocked computes TVL deltas by domain (channels, app_sessions) and asset, upserts as lifespan metrics, and returns updated totals. + GetTotalValueLocked() ([]TotalValueLocked, error) + + // --- User Staked Operations --- + + // UpdateUserStaked upserts the staked amount for a user on a specific blockchain. + UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error + + // GetTotalUserStaked returns the total staked amount for a user across all blockchains. + GetTotalUserStaked(wallet string) (decimal.Decimal, error) + + // --- Action Log Operations --- + + // RecordAction inserts a new action log entry for a user. + RecordAction(wallet string, gatedAction core.GatedAction) error + + // GetUserActionCount returns the number of actions matching the given wallet, method, and path + // within the specified time window. + GetUserActionCount(wallet string, gatedAction core.GatedAction, window time.Duration) (uint64, error) - // CountChannelsByStatus returns channel counts grouped by (asset, status). - CountChannelsByStatus() ([]ChannelCount, error) + // GetUserActionCounts returns a map of gated actions to their respective counts for a user within the specified time window. + GetUserActionCounts(userWallet string, window time.Duration) (map[core.GatedAction]uint64, error) // --- Contract Event Operations --- diff --git a/clearnode/store/database/lifespan_metric.go b/clearnode/store/database/lifespan_metric.go new file mode 100644 index 000000000..db3498d9d --- /dev/null +++ b/clearnode/store/database/lifespan_metric.go @@ -0,0 +1,439 @@ +package database + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/shopspring/decimal" + "gorm.io/datatypes" + "gorm.io/gorm" +) + +type LifespanMetric struct { + ID string `gorm:"column:id;primaryKey;size:66"` + Name string `gorm:"column:name;not null"` + Labels datatypes.JSON `gorm:"column:labels;type:text"` + Value decimal.Decimal `gorm:"column:value;type:varchar(78);not null"` + LastTimestamp time.Time `gorm:"column:last_timestamp;not null"` + UpdatedAt time.Time +} + +func (LifespanMetric) TableName() string { + return "lifespan_metrics" +} + +// ChannelCount holds the result of a COUNT() GROUP BY query on channels. +type ChannelCount struct { + Asset string `gorm:"column:asset"` + Status core.ChannelStatus `gorm:"column:status"` + Count uint64 `gorm:"column:count"` + LastUpdated time.Time `gorm:"column:last_updated"` +} + +// GetChannelsCountByLabels computes channel count deltas since last processed timestamp, +// upserts them as lifespan metrics, and returns the updated totals. +func (s *DBStore) GetChannelsCountByLabels() ([]ChannelCount, error) { + metricName := "channels_total" + + lastProcessedTimestamp, err := s.GetLifetimeMetricLastTimestamp(metricName) + if err != nil { + return nil, fmt.Errorf("failed to get last processed timestamp: %w", err) + } + + var deltas []ChannelCount + err = s.db.Raw(` + SELECT asset, + status AS status, + COUNT(channel_id)::bigint AS count, + MAX(updated_at) AS last_updated + FROM channels + WHERE updated_at > ? + GROUP BY asset, status + `, lastProcessedTimestamp).Scan(&deltas).Error + if err != nil { + return nil, fmt.Errorf("failed to compute channel deltas: %w", err) + } + + if len(deltas) > 0 { + now := time.Now() + valuesSQL := make([]string, 0, len(deltas)) + args := make([]any, 0, len(deltas)*6) + + for i, d := range deltas { + labelsMap := map[string]string{ + "asset": d.Asset, + "status": d.Status.String(), + } + labelsJSON, err := json.Marshal(labelsMap) + if err != nil { + return nil, fmt.Errorf("failed to marshal labels for asset=%s status=%s: %w", d.Asset, d.Status, err) + } + + id, err := getMetricID(metricName, "asset", d.Asset, "status", d.Status.String()) + if err != nil { + return nil, fmt.Errorf("failed to compute metric ID for asset=%s status=%s: %w", d.Asset, d.Status, err) + } + + deltaValue := decimal.NewFromUint64(d.Count) + base := i * 6 + valuesSQL = append(valuesSQL, + fmt.Sprintf("($%d,$%d,$%d,$%d,$%d,$%d)", + base+1, base+2, base+3, base+4, base+5, base+6, + ), + ) + + args = append(args, + id, // $1 + metricName, // $2 + datatypes.JSON(labelsJSON), // $3 + deltaValue, // $4 + d.LastUpdated, // $5 + now, // $6 + ) + } + + upsertSQL := fmt.Sprintf(` + INSERT INTO lifespan_metrics (id, name, labels, value, last_timestamp, updated_at) + VALUES %s + ON CONFLICT (id) DO UPDATE + SET + value = lifespan_metrics.value + EXCLUDED.value, + last_timestamp = GREATEST(lifespan_metrics.last_timestamp, EXCLUDED.last_timestamp), + updated_at = now() + `, strings.Join(valuesSQL, ",")) + + if err := s.db.Exec(upsertSQL, args...).Error; err != nil { + return nil, fmt.Errorf("failed to upsert lifespan metrics: %w", err) + } + } + + var results []ChannelCount + err = s.db.Raw(` + SELECT labels->>'asset' AS asset, + labels->>'status' AS status, + value::bigint AS count, + last_timestamp AS last_updated + FROM lifespan_metrics + WHERE name = ? + `, metricName).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to read lifespan metrics: %w", err) + } + + return results, nil +} + +// AppSessionCount holds the result of a COUNT() GROUP BY query on app sessions. +type AppSessionCount struct { + Application string `gorm:"column:application_id"` + Status app.AppSessionStatus `gorm:"column:status"` + Count uint64 `gorm:"column:count"` + LastUpdated time.Time `gorm:"column:last_updated"` +} + +// GetAppSessionsCountByLabels computes app session count deltas since last processed timestamp, +// upserts them as lifespan metrics, and returns the updated totals. +func (s *DBStore) GetAppSessionsCountByLabels() ([]AppSessionCount, error) { + metricName := "app_sessions_total" + + lastProcessedTimestamp, err := s.GetLifetimeMetricLastTimestamp(metricName) + if err != nil { + return nil, fmt.Errorf("failed to get last processed timestamp: %w", err) + } + + // 2) Compute deltas since lastProcessedTimestamp. + var deltas []AppSessionCount + err = s.db.Raw(` + SELECT application_id, + status AS status, + COUNT(id)::bigint AS count, + MAX(updated_at) AS last_updated + FROM app_sessions_v1 + WHERE updated_at > ? + GROUP BY application_id, status + `, lastProcessedTimestamp).Scan(&deltas).Error + if err != nil { + return nil, fmt.Errorf("failed to compute app sessions deltas: %w", err) + } + + if len(deltas) > 0 { + now := time.Now() + valuesSQL := make([]string, 0, len(deltas)) + args := make([]any, 0, len(deltas)*6) + + for i, d := range deltas { + labelsMap := map[string]string{ + "application_id": d.Application, + "status": d.Status.String(), + } + labelsJSON, err := json.Marshal(labelsMap) + if err != nil { + return nil, fmt.Errorf("failed to marshal labels for application_id=%s status=%s: %w", d.Application, d.Status, err) + } + + id, err := getMetricID(metricName, "application_id", d.Application, "status", d.Status.String()) + if err != nil { + return nil, fmt.Errorf("failed to compute metric ID for application_id=%s status=%s: %w", d.Application, d.Status, err) + } + + deltaValue := decimal.NewFromUint64(d.Count) + base := i * 6 + valuesSQL = append(valuesSQL, + fmt.Sprintf("($%d,$%d,$%d,$%d,$%d,$%d)", + base+1, base+2, base+3, base+4, base+5, base+6, + ), + ) + + args = append(args, + id, // $1 + metricName, // $2 + datatypes.JSON(labelsJSON), // $3 + deltaValue, // $4 + d.LastUpdated, // $5 + now, // $6 + ) + } + + upsertSQL := fmt.Sprintf(` + INSERT INTO lifespan_metrics (id, name, labels, value, last_timestamp, updated_at) + VALUES %s + ON CONFLICT (id) DO UPDATE + SET + value = lifespan_metrics.value + EXCLUDED.value, + last_timestamp = GREATEST(lifespan_metrics.last_timestamp, EXCLUDED.last_timestamp), + updated_at = now() + `, strings.Join(valuesSQL, ",")) + + if err := s.db.Exec(upsertSQL, args...).Error; err != nil { + return nil, fmt.Errorf("failed to upsert lifespan metrics: %w", err) + } + } + + var results []AppSessionCount + err = s.db.Raw(` + SELECT labels->>'application_id' AS application_id, + labels->>'status' AS status, + value::bigint AS count, + last_timestamp AS last_updated + FROM lifespan_metrics + WHERE name = ? + `, metricName).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to read lifespan metrics: %w", err) + } + + return results, nil +} + +// TotalValueLocked holds the total value locked for a given asset, along with the last update timestamp. +type TotalValueLocked struct { + Asset string `gorm:"column:asset"` + Domain string `gorm:"column:domain"` + Value decimal.Decimal `gorm:"column:value"` + LastUpdated time.Time `gorm:"column:last_updated"` +} + +func (s *DBStore) GetTotalValueLocked() ([]TotalValueLocked, error) { + metricName := "total_value_locked" + + lastProcessedTimestamp, err := s.GetLifetimeMetricLastTimestamp(metricName) + if err != nil { + return nil, fmt.Errorf("failed to get last processed timestamp: %w", err) + } + + // Compute net TVL deltas since lastProcessedTimestamp: + // - channels: deposits (tx_type=10) minus withdrawals (tx_type=11) + // - app_sessions: commits (tx_type=40) minus releases (tx_type=41) + var deltas []TotalValueLocked + err = s.db.Raw(` + SELECT domain, asset_symbol AS asset, SUM(net) AS value, MAX(created_at) AS last_updated + FROM ( + SELECT 'channels' AS domain, asset_symbol, + CASE WHEN tx_type = ? THEN amount ELSE -amount END AS net, + created_at + FROM transactions + WHERE tx_type IN (?, ?) AND created_at > ? + UNION ALL + SELECT 'app_sessions' AS domain, asset_symbol, + CASE WHEN tx_type = ? THEN amount ELSE -amount END AS net, + created_at + FROM transactions + WHERE tx_type IN (?, ?) AND created_at > ? + ) t + GROUP BY domain, asset_symbol + `, + core.TransactionTypeHomeDeposit, core.TransactionTypeHomeDeposit, core.TransactionTypeHomeWithdrawal, lastProcessedTimestamp, + core.TransactionTypeCommit, core.TransactionTypeCommit, core.TransactionTypeRelease, lastProcessedTimestamp, + ).Scan(&deltas).Error + if err != nil { + return nil, fmt.Errorf("failed to compute TVL deltas: %w", err) + } + + if len(deltas) > 0 { + now := time.Now() + valuesSQL := make([]string, 0, len(deltas)) + args := make([]any, 0, len(deltas)*6) + + for i, d := range deltas { + labelsMap := map[string]string{ + "domain": d.Domain, + "asset": d.Asset, + } + labelsJSON, err := json.Marshal(labelsMap) + if err != nil { + return nil, fmt.Errorf("failed to marshal labels for domain=%s asset=%s: %w", d.Domain, d.Asset, err) + } + + id, err := getMetricID(metricName, "domain", d.Domain, "asset", d.Asset) + if err != nil { + return nil, fmt.Errorf("failed to compute metric ID for domain=%s asset=%s: %w", d.Domain, d.Asset, err) + } + + base := i * 6 + valuesSQL = append(valuesSQL, + fmt.Sprintf("($%d,$%d,$%d,$%d,$%d,$%d)", + base+1, base+2, base+3, base+4, base+5, base+6, + ), + ) + + args = append(args, + id, // $1 + metricName, // $2 + datatypes.JSON(labelsJSON), // $3 + d.Value, // $4 + d.LastUpdated, // $5 + now, // $6 + ) + } + + upsertSQL := fmt.Sprintf(` + INSERT INTO lifespan_metrics (id, name, labels, value, last_timestamp, updated_at) + VALUES %s + ON CONFLICT (id) DO UPDATE + SET + value = lifespan_metrics.value + EXCLUDED.value, + last_timestamp = GREATEST(lifespan_metrics.last_timestamp, EXCLUDED.last_timestamp), + updated_at = now() + `, strings.Join(valuesSQL, ",")) + + if err := s.db.Exec(upsertSQL, args...).Error; err != nil { + return nil, fmt.Errorf("failed to upsert lifespan metrics: %w", err) + } + } + + var results []TotalValueLocked + err = s.db.Raw(` + SELECT labels->>'domain' AS domain, + labels->>'asset' AS asset, + value, + last_timestamp AS last_updated + FROM lifespan_metrics + WHERE name = ? + `, metricName).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to read lifespan metrics: %w", err) + } + + return results, nil +} + +// CountActiveUsers returns the number of distinct users who had channel state updates +// within the given duration, grouped by asset. If asset is empty, counts across all assets. +// ActiveCountByLabel holds a count grouped by a label (asset or application_id). +type ActiveCountByLabel struct { + Label string `gorm:"column:label"` + Count uint64 `gorm:"column:count"` +} + +// CountActiveUsers returns distinct user counts per asset and an "all" aggregate +// for users with channel state updates within the given window. +func (s *DBStore) CountActiveUsers(window time.Duration) ([]ActiveCountByLabel, error) { + since := time.Now().Add(-window) + + var results []ActiveCountByLabel + err := s.db.Raw(` + SELECT asset AS label, COUNT(DISTINCT user_wallet) AS count + FROM user_balances + WHERE updated_at > ? + GROUP BY asset + `, since).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to count active users: %w", err) + } + + // "ALL" aggregate: distinct users across all assets. + var total uint64 + err = s.db.Model(&UserBalance{}). + Select("COUNT(DISTINCT user_wallet)"). + Where("updated_at > ?", since). + Scan(&total).Error + if err != nil { + return nil, fmt.Errorf("failed to count total active users: %w", err) + } + + results = append(results, ActiveCountByLabel{Label: "ALL", Count: total}) + return results, nil +} + +// CountActiveAppSessions returns app session counts per application within the given window. +func (s *DBStore) CountActiveAppSessions(window time.Duration) ([]ActiveCountByLabel, error) { + since := time.Now().Add(-window) + + var results []ActiveCountByLabel + err := s.db.Raw(` + SELECT application_id AS label, COUNT(id) AS count + FROM app_sessions_v1 + WHERE updated_at > ? + GROUP BY application_id + `, since).Scan(&results).Error + if err != nil { + return nil, fmt.Errorf("failed to count active app sessions: %w", err) + } + + return results, nil +} + +// GetLifetimeMetricLastTimestamp returns the most recent last_timestamp among all metrics with the given name. +func (s *DBStore) GetLifetimeMetricLastTimestamp(name string) (time.Time, error) { + var metric LifespanMetric + err := s.db.Where("name = ?", name). + Order("last_timestamp DESC"). + First(&metric).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return time.Time{}, nil + } + return time.Time{}, fmt.Errorf("failed to get metric last timestamp: %w", err) + } + + return metric.LastTimestamp, nil +} + +func getMetricID(name string, labels ...string) (string, error) { + var labelsArray = []string{} + labelsArray = append(labelsArray, labels...) + + stringTy, _ := abi.NewType("string", "", nil) + stringSliceTy, _ := abi.NewType("string[]", "", nil) + args := abi.Arguments{ + {Type: stringTy}, // name + {Type: stringSliceTy}, // labels array + } + + packed, err := args.Pack( + name, + labelsArray, + ) + if err != nil { + return "", fmt.Errorf("failed to pack app session request: %w", err) + } + + return hexutil.Encode(crypto.Keccak256(packed)), nil +} diff --git a/clearnode/store/database/lifespan_metric_test.go b/clearnode/store/database/lifespan_metric_test.go new file mode 100644 index 000000000..2ca2521b3 --- /dev/null +++ b/clearnode/store/database/lifespan_metric_test.go @@ -0,0 +1,243 @@ +package database + +import ( + "testing" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetMetricID(t *testing.T) { + t.Run("deterministic ID", func(t *testing.T) { + id1, err := getMetricID("test_metric", "key1", "val1") + require.NoError(t, err) + + id2, err := getMetricID("test_metric", "key1", "val1") + require.NoError(t, err) + + assert.Equal(t, id1, id2) + }) + + t.Run("different labels produce different IDs", func(t *testing.T) { + id1, err := getMetricID("test_metric", "key1", "val1") + require.NoError(t, err) + + id2, err := getMetricID("test_metric", "key1", "val2") + require.NoError(t, err) + + assert.NotEqual(t, id1, id2) + }) + + t.Run("different names produce different IDs", func(t *testing.T) { + id1, err := getMetricID("metric_a") + require.NoError(t, err) + + id2, err := getMetricID("metric_b") + require.NoError(t, err) + + assert.NotEqual(t, id1, id2) + }) + + t.Run("no labels", func(t *testing.T) { + id, err := getMetricID("metric_no_labels") + require.NoError(t, err) + assert.NotEmpty(t, id) + }) + + t.Run("ID starts with 0x", func(t *testing.T) { + id, err := getMetricID("test") + require.NoError(t, err) + assert.Equal(t, "0x", id[:2]) + }) +} + +func TestGetLifetimeMetricLastTimestamp(t *testing.T) { + t.Run("no metrics returns zero time", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + ts, err := store.GetLifetimeMetricLastTimestamp("nonexistent") + require.NoError(t, err) + assert.True(t, ts.IsZero()) + }) + + t.Run("returns most recent timestamp", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + ts1 := time.Now().Add(-2 * time.Hour).Truncate(time.Second) + ts2 := time.Now().Add(-1 * time.Hour).Truncate(time.Second) + ts3 := time.Now().Truncate(time.Second) + + db.Create(&LifespanMetric{ID: "id-a", Name: "my_metric", Value: decimal.NewFromInt(1), LastTimestamp: ts1}) + db.Create(&LifespanMetric{ID: "id-b", Name: "my_metric", Value: decimal.NewFromInt(2), LastTimestamp: ts3}) + db.Create(&LifespanMetric{ID: "id-c", Name: "my_metric", Value: decimal.NewFromInt(3), LastTimestamp: ts2}) + + latest, err := store.GetLifetimeMetricLastTimestamp("my_metric") + require.NoError(t, err) + assert.Equal(t, ts3.UTC(), latest.UTC()) + }) + + t.Run("scoped to metric name", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + tsOld := time.Now().Add(-time.Hour).Truncate(time.Second) + tsNew := time.Now().Truncate(time.Second) + + db.Create(&LifespanMetric{ID: "id-1", Name: "metric_a", Value: decimal.NewFromInt(1), LastTimestamp: tsOld}) + db.Create(&LifespanMetric{ID: "id-2", Name: "metric_b", Value: decimal.NewFromInt(1), LastTimestamp: tsNew}) + + latest, err := store.GetLifetimeMetricLastTimestamp("metric_a") + require.NoError(t, err) + assert.Equal(t, tsOld.UTC(), latest.UTC()) + }) +} + +func TestCountActiveUsers(t *testing.T) { + t.Run("no data returns only ALL with zero", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + results, err := store.CountActiveUsers(24 * time.Hour) + require.NoError(t, err) + + require.Len(t, results, 1) + assert.Equal(t, "ALL", results[0].Label) + assert.Equal(t, uint64(0), results[0].Count) + }) + + t.Run("counts distinct users per asset", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + now := time.Now() + db.Create(&UserBalance{UserWallet: "0xuser1", Asset: "USDC", Balance: decimal.NewFromInt(100), UpdatedAt: now}) + db.Create(&UserBalance{UserWallet: "0xuser2", Asset: "USDC", Balance: decimal.NewFromInt(200), UpdatedAt: now}) + db.Create(&UserBalance{UserWallet: "0xuser1", Asset: "ETH", Balance: decimal.NewFromInt(50), UpdatedAt: now}) + + results, err := store.CountActiveUsers(24 * time.Hour) + require.NoError(t, err) + + require.Len(t, results, 3) + + countByLabel := make(map[string]uint64) + for _, r := range results { + countByLabel[r.Label] = r.Count + } + + assert.Equal(t, uint64(2), countByLabel["USDC"]) + assert.Equal(t, uint64(1), countByLabel["ETH"]) + assert.Equal(t, uint64(2), countByLabel["ALL"]) + }) + + t.Run("respects time window", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + old := time.Now().Add(-48 * time.Hour) + recent := time.Now() + db.Create(&UserBalance{UserWallet: "0xold", Asset: "USDC", Balance: decimal.NewFromInt(100), UpdatedAt: old}) + db.Create(&UserBalance{UserWallet: "0xnew", Asset: "USDC", Balance: decimal.NewFromInt(200), UpdatedAt: recent}) + + results, err := store.CountActiveUsers(24 * time.Hour) + require.NoError(t, err) + + countByLabel := make(map[string]uint64) + for _, r := range results { + countByLabel[r.Label] = r.Count + } + + assert.Equal(t, uint64(1), countByLabel["USDC"]) + assert.Equal(t, uint64(1), countByLabel["ALL"]) + }) +} + +func TestCountActiveAppSessions(t *testing.T) { + t.Run("no data returns empty", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + results, err := store.CountActiveAppSessions(24 * time.Hour) + require.NoError(t, err) + assert.Empty(t, results) + }) + + t.Run("counts sessions per application", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + now := time.Now() + db.Create(&AppSessionV1{ID: "s1", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s2", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 2, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s3", ApplicationID: "app2", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}) + + results, err := store.CountActiveAppSessions(24 * time.Hour) + require.NoError(t, err) + + countByLabel := make(map[string]uint64) + for _, r := range results { + countByLabel[r.Label] = r.Count + } + + assert.Equal(t, uint64(2), countByLabel["app1"]) + assert.Equal(t, uint64(1), countByLabel["app2"]) + }) + + t.Run("respects time window", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + old := time.Now().Add(-48 * time.Hour) + recent := time.Now() + db.Create(&AppSessionV1{ID: "s1", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: old}) + db.Create(&AppSessionV1{ID: "s2", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 2, UpdatedAt: recent}) + + results, err := store.CountActiveAppSessions(24 * time.Hour) + require.NoError(t, err) + + countByLabel := make(map[string]uint64) + for _, r := range results { + countByLabel[r.Label] = r.Count + } + + assert.Equal(t, uint64(1), countByLabel["app1"]) + }) + + t.Run("multiple applications with mixed statuses", func(t *testing.T) { + db, cleanup := SetupTestDB(t) + defer cleanup() + store := &DBStore{db: db} + + now := time.Now() + db.Create(&AppSessionV1{ID: "s1", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s2", ApplicationID: "app1", SessionData: "{}", Status: app.AppSessionStatusClosed, Nonce: 2, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s3", ApplicationID: "app2", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 1, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s4", ApplicationID: "app2", SessionData: "{}", Status: app.AppSessionStatusOpen, Nonce: 2, UpdatedAt: now}) + db.Create(&AppSessionV1{ID: "s5", ApplicationID: "app2", SessionData: "{}", Status: app.AppSessionStatusClosed, Nonce: 3, UpdatedAt: now}) + + results, err := store.CountActiveAppSessions(24 * time.Hour) + require.NoError(t, err) + + countByLabel := make(map[string]uint64) + for _, r := range results { + countByLabel[r.Label] = r.Count + } + + // CountActiveAppSessions counts all sessions regardless of status + assert.Equal(t, uint64(2), countByLabel["app1"]) + assert.Equal(t, uint64(3), countByLabel["app2"]) + }) +} diff --git a/clearnode/store/database/state.go b/clearnode/store/database/state.go index 9f62911ab..b4191a7e1 100644 --- a/clearnode/store/database/state.go +++ b/clearnode/store/database/state.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "gorm.io/gorm" ) diff --git a/clearnode/store/database/state_test.go b/clearnode/store/database/state_test.go index aab572bd7..08e6e5ddc 100644 --- a/clearnode/store/database/state_test.go +++ b/clearnode/store/database/state_test.go @@ -3,7 +3,7 @@ package database import ( "testing" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/clearnode/store/database/test/postgres_integration_test.go b/clearnode/store/database/test/postgres_integration_test.go index 59a44a316..fb6a01f42 100644 --- a/clearnode/store/database/test/postgres_integration_test.go +++ b/clearnode/store/database/test/postgres_integration_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/clearnode/store/database" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/clearnode/store/database" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -254,9 +254,9 @@ func TestPostgres_AppSessionOperations(t *testing.T) { t.Run("Create and retrieve app session", func(t *testing.T) { session := app.AppSessionV1{ - SessionID: "0x7234567890123456789012345678901234567890123456789012345678901234", - Application: "poker", - Nonce: 1, + SessionID: "0x7234567890123456789012345678901234567890123456789012345678901234", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0x7334567890123456789012345678901234567890", @@ -283,7 +283,7 @@ func TestPostgres_AppSessionOperations(t *testing.T) { require.NotNil(t, retrieved) assert.Equal(t, session.SessionID, retrieved.SessionID) - assert.Equal(t, session.Application, retrieved.Application) + assert.Equal(t, session.ApplicationID, retrieved.ApplicationID) assert.Len(t, retrieved.Participants, 2) }) @@ -291,9 +291,9 @@ func TestPostgres_AppSessionOperations(t *testing.T) { participant := "0x7534567890123456789012345678901234567890" session := app.AppSessionV1{ - SessionID: "0x7634567890123456789012345678901234567890123456789012345678901234", - Application: "poker", - Nonce: 1, + SessionID: "0x7634567890123456789012345678901234567890123456789012345678901234", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: participant, @@ -319,9 +319,9 @@ func TestPostgres_AppSessionOperations(t *testing.T) { t.Run("Update app session", func(t *testing.T) { session := app.AppSessionV1{ - SessionID: "0x7734567890123456789012345678901234567890123456789012345678901234", - Application: "poker", - Nonce: 1, + SessionID: "0x7734567890123456789012345678901234567890123456789012345678901234", + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ { WalletAddress: "0x7834567890123456789012345678901234567890", @@ -380,9 +380,9 @@ func TestPostgres_AppLedgerOperations(t *testing.T) { // Create session with participants session := app.AppSessionV1{ - SessionID: sessionID, - Application: "poker", - Nonce: 1, + SessionID: sessionID, + ApplicationID: "poker", + Nonce: 1, Participants: []app.AppParticipantV1{ {WalletAddress: wallet1, SignatureWeight: 50}, {WalletAddress: wallet2, SignatureWeight: 50}, diff --git a/clearnode/store/database/testing.go b/clearnode/store/database/testing.go index ffc7cd669..1f54bc26a 100644 --- a/clearnode/store/database/testing.go +++ b/clearnode/store/database/testing.go @@ -54,7 +54,7 @@ func setupTestSqlite(t testing.TB) *gorm.DB { t.Fatalf("Failed to open SQLite database: %v", err) } - err = database.AutoMigrate(&AppLedgerEntryV1{}, &AppSessionV1{}, &AppParticipantV1{}, &BlockchainAction{}, &Channel{}, &ContractEvent{}, &State{}, &Transaction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}) + err = database.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &AppSessionV1{}, &AppParticipantV1{}, &BlockchainAction{}, &Channel{}, &ContractEvent{}, &State{}, &Transaction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}) if err != nil { t.Fatalf("Failed to run migrations: %v", err) } @@ -99,7 +99,7 @@ func setupTestPostgres(ctx context.Context, t testing.TB) (*gorm.DB, testcontain t.Fatalf("Failed to open PostgreSQL database: %v", err) } - err = database.AutoMigrate(&AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &Transaction{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}) + err = database.AutoMigrate(&AppV1{}, &AppLedgerEntryV1{}, &Channel{}, &AppSessionV1{}, &ContractEvent{}, &Transaction{}, &BlockchainAction{}, &AppSessionKeyStateV1{}, &AppSessionKeyApplicationV1{}, &AppSessionKeyAppSessionIDV1{}, &ChannelSessionKeyStateV1{}, &ChannelSessionKeyAssetV1{}, &UserBalance{}, &UserStakedV1{}, &ActionLogEntryV1{}, &LifespanMetric{}) if err != nil { t.Fatalf("Failed to run migrations: %v", err) } diff --git a/clearnode/store/database/transaction.go b/clearnode/store/database/transaction.go index fa74ffb77..dbd1c8daa 100644 --- a/clearnode/store/database/transaction.go +++ b/clearnode/store/database/transaction.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) diff --git a/clearnode/store/database/transaction_test.go b/clearnode/store/database/transaction_test.go index 1f3c68109..f0a604f93 100644 --- a/clearnode/store/database/transaction_test.go +++ b/clearnode/store/database/transaction_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/clearnode/store/database/user_staked.go b/clearnode/store/database/user_staked.go new file mode 100644 index 000000000..01db0fe18 --- /dev/null +++ b/clearnode/store/database/user_staked.go @@ -0,0 +1,75 @@ +package database + +import ( + "fmt" + "strings" + "time" + + "github.com/shopspring/decimal" + "gorm.io/gorm/clause" +) + +type UserStakedV1 struct { + UserWallet string `gorm:"column:user_wallet;primaryKey;not null"` + BlockchainID uint64 `gorm:"column:blockchain_id;primaryKey;not null"` + Amount decimal.Decimal `gorm:"column:amount;type:varchar(78);not null"` + CreatedAt time.Time + UpdatedAt time.Time +} + +func (UserStakedV1) TableName() string { + return "user_staked_v1" +} + +// UpdateUserStaked upserts the staked amount for a user on a specific blockchain. +func (s *DBStore) UpdateUserStaked(wallet string, blockchainID uint64, amount decimal.Decimal) error { + wallet = strings.ToLower(wallet) + + if wallet == "" { + return fmt.Errorf("wallet address must not be empty") + } + if blockchainID == 0 { + return fmt.Errorf("blockchain ID must not be zero") + } + if amount.IsNegative() { + return fmt.Errorf("staked amount must not be negative") + } + + now := time.Now() + + record := UserStakedV1{ + UserWallet: wallet, + BlockchainID: blockchainID, + Amount: amount, + CreatedAt: now, + UpdatedAt: now, + } + + err := s.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "user_wallet"}, {Name: "blockchain_id"}}, + DoUpdates: clause.AssignmentColumns([]string{"amount", "updated_at"}), + }).Create(&record).Error + if err != nil { + return fmt.Errorf("failed to update user staked amount: %w", err) + } + + return nil +} + +// GetTotalUserStaked returns the total staked amount for a user across all blockchains. +func (s *DBStore) GetTotalUserStaked(wallet string) (decimal.Decimal, error) { + wallet = strings.ToLower(wallet) + + var result struct { + Total decimal.Decimal `gorm:"column:total"` + } + err := s.db.Model(&UserStakedV1{}). + Where("user_wallet = ?", wallet). + Select("COALESCE(SUM(amount), 0) AS total"). + Scan(&result).Error + if err != nil { + return decimal.Zero, fmt.Errorf("failed to get user staked total: %w", err) + } + + return result.Total, nil +} diff --git a/clearnode/store/database/utils.go b/clearnode/store/database/utils.go index 696de008b..5f7af224e 100644 --- a/clearnode/store/database/utils.go +++ b/clearnode/store/database/utils.go @@ -5,8 +5,8 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" ) // databaseChannelToCore converts database.Channel to core.Channel @@ -38,16 +38,16 @@ func databaseAppSessionToCore(dbSession *AppSessionV1) *app.AppSessionV1 { } return &app.AppSessionV1{ - SessionID: dbSession.ID, - Application: dbSession.Application, - Participants: participants, - Quorum: dbSession.Quorum, - Nonce: dbSession.Nonce, - Status: dbSession.Status, - Version: dbSession.Version, - SessionData: dbSession.SessionData, - CreatedAt: dbSession.CreatedAt, - UpdatedAt: dbSession.UpdatedAt, + SessionID: dbSession.ID, + ApplicationID: dbSession.ApplicationID, + Participants: participants, + Quorum: dbSession.Quorum, + Nonce: dbSession.Nonce, + Status: dbSession.Status, + Version: dbSession.Version, + SessionData: dbSession.SessionData, + CreatedAt: dbSession.CreatedAt, + UpdatedAt: dbSession.UpdatedAt, } } diff --git a/clearnode/store/memory/blockchain_config.go b/clearnode/store/memory/blockchain_config.go index 361907249..27e89f688 100644 --- a/clearnode/store/memory/blockchain_config.go +++ b/clearnode/store/memory/blockchain_config.go @@ -6,6 +6,7 @@ import ( "path/filepath" "regexp" + "github.com/layer-3/nitrolite/pkg/core" "gopkg.in/yaml.v3" ) @@ -23,12 +24,7 @@ var ( // It contains default contract addresses that apply to all blockchains unless overridden, // and a list of individual blockchain configurations. type BlockchainsConfig struct { - DefaultContractAddresses DefaultContractAddresses `yaml:"default_contract_addresses"` - Blockchains []BlockchainConfig `yaml:"blockchains"` -} - -type DefaultContractAddresses struct { - ChannelHub string `yaml:"channel_hub,omitempty"` + Blockchains []BlockchainConfig `yaml:"blockchains"` } // BlockchainConfig represents configuration for a single blockchain. @@ -45,6 +41,10 @@ type BlockchainConfig struct { BlockStep uint64 `yaml:"block_step"` // ChannelHubAddress is the address of the ChannelHub contract on this blockchain ChannelHubAddress string `yaml:"channel_hub_address"` + // ChannelHubSigValidators maps validator IDs to the addresses of signature validators for the ChannelHub contract on this blockchain + ChannelHubSigValidators map[uint8]string `yaml:"channel_hub_sig_validators"` + // LockingContractAddress is the address of the locking contract on this blockchain + LockingContractAddress string `yaml:"locking_contract_address"` } // LoadEnabledBlockchains loads and validates blockchain configurations from a YAML file. @@ -77,10 +77,6 @@ func LoadEnabledBlockchains(configDirPath string) (map[uint64]BlockchainConfig, } func verifyBlockchainsConfig(cfg *BlockchainsConfig) error { - if addr := cfg.DefaultContractAddresses.ChannelHub; !contractAddressRegex.MatchString(addr) && addr != "" { - return fmt.Errorf("invalid default channel hub address '%s'", addr) - } - for i, bc := range cfg.Blockchains { if bc.Disabled { continue @@ -90,19 +86,33 @@ func verifyBlockchainsConfig(cfg *BlockchainsConfig) error { return fmt.Errorf("invalid blockchain name '%s', should match snake_case format", bc.Name) } - if bc.ChannelHubAddress == "" { - if cfg.DefaultContractAddresses.ChannelHub == "" { - return fmt.Errorf("missing default and blockchain-specific channel hub address for blockchain '%s'", bc.Name) - } else { - cfg.Blockchains[i].ChannelHubAddress = cfg.DefaultContractAddresses.ChannelHub - } - } else if !contractAddressRegex.MatchString(bc.ChannelHubAddress) { + if bc.ChannelHubAddress == "" && bc.LockingContractAddress == "" { + return fmt.Errorf("blockchain '%s' must specify at least one of channel_hub_address or locking_contract_address", bc.Name) + } + + if bc.ChannelHubAddress != "" && !contractAddressRegex.MatchString(bc.ChannelHubAddress) { return fmt.Errorf("invalid channel hub address '%s' for blockchain '%s'", bc.ChannelHubAddress, bc.Name) } + if bc.LockingContractAddress != "" && !contractAddressRegex.MatchString(bc.LockingContractAddress) { + return fmt.Errorf("invalid locking contract address '%s' for blockchain '%s'", bc.LockingContractAddress, bc.Name) + } + if bc.BlockStep == 0 { cfg.Blockchains[i].BlockStep = defaultBlockStep } + + if bc.ChannelHubAddress != "" && len(core.ChannelSignerTypes) > 1 { + for _, channelSignerType := range core.ChannelSignerTypes[1:] { + validatorAddress, ok := bc.ChannelHubSigValidators[uint8(channelSignerType)] + if !ok { + return fmt.Errorf("blockchain '%s' must specify a signature validator address for channel signer type %d", bc.Name, channelSignerType) + } + if !contractAddressRegex.MatchString(validatorAddress) { + return fmt.Errorf("invalid signature validator address '%s' for channel signer type %d on blockchain '%s'", validatorAddress, channelSignerType, bc.Name) + } + } + } } return nil diff --git a/clearnode/store/memory/blockchain_config_test.go b/clearnode/store/memory/blockchain_config_test.go index 4a67ea2ba..60dcb579b 100644 --- a/clearnode/store/memory/blockchain_config_test.go +++ b/clearnode/store/memory/blockchain_config_test.go @@ -17,20 +17,18 @@ func TestBlockchainConfig_verifyVariables(t *testing.T) { { name: "valid config", cfg: BlockchainsConfig{ - DefaultContractAddresses: DefaultContractAddresses{ - ChannelHub: "0x0000000000000000000000000000000000000001", - }, - Blockchains: []BlockchainConfig{ { - ID: 1, - Name: "ethereum", - ChannelHubAddress: "0x1111111111111111111111111111111111111111", - BlockStep: 10, + ID: 1, + Name: "ethereum", + ChannelHubAddress: "0x1111111111111111111111111111111111111111", + BlockStep: 10, + ChannelHubSigValidators: map[uint8]string{1: "0x3333333333333333333333333333333333333333"}, }, { - ID: 11155111, - Name: "ethereum_sepolia", + ID: 11155111, + Name: "ethereum_sepolia", + LockingContractAddress: "0x2222222222222222222222222222222222222222", }, }, }, @@ -48,7 +46,7 @@ func TestBlockchainConfig_verifyVariables(t *testing.T) { sepoliaCfg := blockchains[1] assert.Equal(t, "ethereum_sepolia", sepoliaCfg.Name) assert.Equal(t, uint64(11155111), sepoliaCfg.ID) - assert.Equal(t, "0x0000000000000000000000000000000000000001", sepoliaCfg.ChannelHubAddress) + assert.Equal(t, "0x2222222222222222222222222222222222222222", sepoliaCfg.LockingContractAddress) assert.False(t, sepoliaCfg.Disabled) assert.Equal(t, defaultBlockStep, sepoliaCfg.BlockStep) }, @@ -80,14 +78,13 @@ func TestBlockchainConfig_verifyVariables(t *testing.T) { { name: "disabled blockchain", cfg: BlockchainsConfig{ - DefaultContractAddresses: DefaultContractAddresses{ - ChannelHub: "0x0000000000000000000000000000000000000001", - }, Blockchains: []BlockchainConfig{ { - ID: 1, - Name: "ethereum", - Disabled: false, + ID: 1, + Name: "ethereum", + Disabled: false, + ChannelHubAddress: "0x1111111111111111111111111111111111111111", + ChannelHubSigValidators: map[uint8]string{1: "0x3333333333333333333333333333333333333333"}, }, { ID: 11155111, @@ -109,28 +106,6 @@ func TestBlockchainConfig_verifyVariables(t *testing.T) { assert.Equal(t, uint64(11155111), sepoliaCfg.ID) }, }, - { - name: "invalid default channel hub address", - cfg: BlockchainsConfig{ - DefaultContractAddresses: DefaultContractAddresses{ - ChannelHub: "0x0000s00000000000000000000000000000000001", - }, - }, - expectedErrorStr: "invalid default channel hub address '0x0000s00000000000000000000000000000000001'", - }, - { - name: "missing channel hub address", - cfg: BlockchainsConfig{ - Blockchains: []BlockchainConfig{ - { - ID: 1, - Name: "ethereum", - ChannelHubAddress: "", - }, - }, - }, - expectedErrorStr: "missing default and blockchain-specific channel hub address for blockchain 'ethereum'", - }, { name: "invalid channel hub address", cfg: BlockchainsConfig{ diff --git a/clearnode/store/memory/interface.go b/clearnode/store/memory/interface.go index be05d9f67..5f0aef35d 100644 --- a/clearnode/store/memory/interface.go +++ b/clearnode/store/memory/interface.go @@ -1,7 +1,7 @@ package memory import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // MemoryStore defines an in-memory data store interface for retrieving @@ -14,6 +14,9 @@ type MemoryStore interface { // If blockchainID is provided, filters assets to only include tokens on that blockchain. GetAssets(blockchainID *uint64) ([]core.Asset, error) + // GetChannelSigValidators retrieves the channel signature validators for a specific blockchain. + GetChannelSigValidators(blockchainID uint64) (map[uint8]string, error) + // GetTokenAddress retrieves the token address for a given asset on a specific blockchain. GetTokenAddress(asset string, blockchainID uint64) (string, error) diff --git a/clearnode/store/memory/memory_store.go b/clearnode/store/memory/memory_store.go index d4e27c9a1..18b572e86 100644 --- a/clearnode/store/memory/memory_store.go +++ b/clearnode/store/memory/memory_store.go @@ -5,31 +5,38 @@ import ( "slices" "strings" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) type MemoryStoreV1 struct { - blockchains []core.Blockchain - assets []core.Asset - supportedAssets map[string]map[uint64]string // map[asset]map[blockchain_id]string - tokenDecimals map[uint64]map[string]uint8 // map[blockchain_id]map[token_address]decimals - assetDecimals map[string]uint8 // map[asset]decimals + blockchains []core.Blockchain + assets []core.Asset + channelSigValidators map[uint64]map[uint8]string // map[blockchain_id]map[validator_id]validator_address + supportedAssets map[string]map[uint64]string // map[asset]map[blockchain_id]string + tokenDecimals map[uint64]map[string]uint8 // map[blockchain_id]map[token_address]decimals + assetDecimals map[string]uint8 // map[asset]decimals } func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]BlockchainConfig) (MemoryStore, error) { supportedBlockchainIDs := make(map[uint64]struct{}) blockchains := make([]core.Blockchain, 0, len(blockchainsConfig)) + channelSigValidators := make(map[uint64]map[uint8]string) for _, bc := range blockchainsConfig { if bc.Disabled { continue } - supportedBlockchainIDs[bc.ID] = struct{}{} + + if bc.ChannelHubAddress != "" { + supportedBlockchainIDs[bc.ID] = struct{}{} + channelSigValidators[bc.ID] = bc.ChannelHubSigValidators + } blockchains = append(blockchains, core.Blockchain{ - ID: bc.ID, - Name: bc.Name, - ChannelHubAddress: bc.ChannelHubAddress, - BlockStep: bc.BlockStep, + ID: bc.ID, + Name: bc.Name, + ChannelHubAddress: bc.ChannelHubAddress, + LockingContractAddress: bc.LockingContractAddress, + BlockStep: bc.BlockStep, }) } slices.SortFunc(blockchains, func(a, b core.Blockchain) int { @@ -120,11 +127,12 @@ func NewMemoryStoreV1(assetsConfig AssetsConfig, blockchainsConfig map[uint64]Bl }) return &MemoryStoreV1{ - blockchains: blockchains, - assets: assets, - supportedAssets: supportedAssets, - tokenDecimals: tokenDecimals, - assetDecimals: assetDecimals, + blockchains: blockchains, + assets: assets, + channelSigValidators: channelSigValidators, + supportedAssets: supportedAssets, + tokenDecimals: tokenDecimals, + assetDecimals: assetDecimals, }, nil } @@ -171,6 +179,14 @@ func (ms *MemoryStoreV1) GetAssets(blockchainID *uint64) ([]core.Asset, error) { return filteredAssets, nil } +func (ms *MemoryStoreV1) GetChannelSigValidators(blockchainID uint64) (map[uint8]string, error) { + channelSigValidators, ok := ms.channelSigValidators[blockchainID] + if !ok { + return nil, fmt.Errorf("blockchain with ID '%d' is not supported", blockchainID) + } + return channelSigValidators, nil +} + func (ms *MemoryStoreV1) GetTokenAddress(asset string, blockchainID uint64) (string, error) { tokensOnchain, ok := ms.supportedAssets[asset] if !ok { diff --git a/clearnode/stress/app_session.go b/clearnode/stress/app_session.go index f622e6f87..085515445 100644 --- a/clearnode/stress/app_session.go +++ b/clearnode/stress/app_session.go @@ -13,8 +13,8 @@ import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/sign" ) type pipe struct { @@ -72,10 +72,10 @@ func preGenerateSigs(p *pipe, numParticipants, numOperates int, nonce uint64, as } definition := app.AppDefinitionV1{ - Application: "stress-test", - Participants: participants, - Quorum: uint8(numParticipants), - Nonce: nonce, + ApplicationID: "stress-test", + Participants: participants, + Quorum: uint8(numParticipants), + Nonce: nonce, } sessionID, err := app.GenerateAppSessionIDV1(definition) diff --git a/clearnode/stress/config.go b/clearnode/stress/config.go index 6a1c64833..d9241baee 100644 --- a/clearnode/stress/config.go +++ b/clearnode/stress/config.go @@ -10,7 +10,7 @@ import ( ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ilyakaznacheev/cleanenv" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/sign" ) // Config holds all stress test settings, read from environment variables. diff --git a/clearnode/stress/methods.go b/clearnode/stress/methods.go index 11ea73f25..6aca3e709 100644 --- a/clearnode/stress/methods.go +++ b/clearnode/stress/methods.go @@ -5,8 +5,8 @@ import ( "fmt" "strconv" - "github.com/erc7824/nitrolite/pkg/core" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + sdk "github.com/layer-3/nitrolite/sdk/go" ) // MethodRegistry returns all available stress test methods. diff --git a/clearnode/stress/pool.go b/clearnode/stress/pool.go index cd70071d9..a84050f39 100644 --- a/clearnode/stress/pool.go +++ b/clearnode/stress/pool.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" ) // CreateClientPool opens up to n WebSocket connections to the clearnode. diff --git a/clearnode/stress/runner.go b/clearnode/stress/runner.go index 457900b8d..7bc26ed1b 100644 --- a/clearnode/stress/runner.go +++ b/clearnode/stress/runner.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "time" - sdk "github.com/erc7824/nitrolite/sdk/go" + sdk "github.com/layer-3/nitrolite/sdk/go" ) // RunTest executes totalReqs calls of fn distributed across the client pool. diff --git a/clearnode/stress/transfer.go b/clearnode/stress/transfer.go index 97df7ae57..d75467d07 100644 --- a/clearnode/stress/transfer.go +++ b/clearnode/stress/transfer.go @@ -13,9 +13,9 @@ import ( "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" ) type wallet struct { diff --git a/clearnode/stress/types.go b/clearnode/stress/types.go index 9891b36e7..912b1e663 100644 --- a/clearnode/stress/types.go +++ b/clearnode/stress/types.go @@ -4,7 +4,7 @@ import ( "context" "time" - sdk "github.com/erc7824/nitrolite/sdk/go" + sdk "github.com/layer-3/nitrolite/sdk/go" ) // Runner is the unified signature for executing a stress test. diff --git a/contracts/README.md b/contracts/README.md index 9265b4558..681c2a8d6 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -4,10 +4,10 @@ Foundry consists of: -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. ## Documentation @@ -64,3 +64,57 @@ $ forge --help $ anvil --help $ cast --help ``` + +## Parametric Tokens + +Nitrolite supports tokens with additional parameters (e.g., mintTime) through the `IParametricToken` interface. These tokens maintain separate balances per sub-account to preserve parameter integrity. `ParametricToken` contract provides implementation of a token with both mutable and immutable parameters. + +### How It Works + +When a token is marked parametric, the ChannelHub contract: + +1. Converts its own account to Super account on the token +2. Creates a new sub-account for each channel at channel creation time +3. Stores the sub-account ID in channel metadata + +All subsequent deposits, withdrawals, and transfers for that channel automatically use the correct sub-account. + +### Important: Channel Must Exist First + +For parametric tokens, funds **cannot be deposited before channel creation**. The workflow is: + +1. **Create channel** → ChannelHub creates a sub-account and returns channel ID +2. **Deposit** → Funds go to the channel's sub-account +3. **Transfer/Withdraw** → Funds move from/to the sub-account + +Depositing a parametric token without an existing channel leads to token lock and requires reclaim. + +### Enabling Parametric Token Support + +The vault contract owner must perform two steps: + +```solidity +// Step 1: Mark token as parametric +channelHub.setParametricToken(tokenAddress, true); + +// Step 2: Convert ChannelHub to Super account on the token +IParametricToken(tokenAddress).convertToSuper(address(channelHub)); +``` + +After this, channel creation and deposits work through the standard NitroliteClient API - no additional user action required. + +### Low-Level Access + +For advanced use cases, the `IParametricToken` interface exposes direct sub-account operations: + +- `transferToSub()` - Transfer from normal account to a vault sub-account + +- `transferFromSub()` - Transfer from a vault sub-account to normal account + +- `transferBetweenSubs()` - Transfer between sub-accounts of the same super account (including vault) + +These are intended for custom integrations and use `subId` for sub-account identification; standard channel operations handle sub-accounts automatically. + +### Standard ERC20 Tokens + +For non-parametric tokens (USDC, ETH, etc.), the `isParametricToken` flag is disabled by default and no sub-accounts are created. diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1772892720931.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1772892720931.json new file mode 100644 index 000000000..5af57197a --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-1772892720931.json @@ -0,0 +1,188 @@ +{ + "transactions": [ + { + "hash": "0x3df2187dc8a50ef62abfeb377318888493042770315492070c4708584dfbf572", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1bb70c", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576116f0908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b604060031936011261122d5760043567ffffffffffffffff811161122d5760a0600319823603011261122d5760a0820182811067ffffffffffffffff821117611299576040528060040135600681101561122d578252602481013567ffffffffffffffff811161122d5761009f90600436918401016113e1565b602083019081526040830192604483013584526100c96084606083019460648101358652016112ec565b6080820190815260243567ffffffffffffffff811161122d576100f09036906004016113e1565b6100f8611498565b50606081019367ffffffffffffffff855151164603610dc45767ffffffffffffffff82511681519067ffffffffffffffff82511610908115611261575b5015610a085784516040810190601260ff83511611611239574667ffffffffffffffff825116146110ff575b505060208201928351600a8110156103585760041480156110eb575b80156110d7575b80156110c3575b80156110af575b801561109b575b1561105e576080830167ffffffffffffffff815151161561103657515167ffffffffffffffff16461461100e575b6101dc865160a06060820151910151906114e6565b6101f1875160c06080820151910151906114f3565b5f8112610fe65761020190611526565b03610fbe578451600681101561035857600214610f7f575b50610222611498565b5061023c608086510151608060608451015101519061150e565b9061025660c08751015160c060608451015101519061150e565b9351600a81101561035857600281036104b65750509050610275611498565b928051600681101561035857159081156104a0575b811561048a575b8115610475575b501561044d575f8113156104255782526020820152600160408201525f6060820152925b6102d96102d1608086019260018452516115a7565b8551906114f3565b926102ea60208601948551906114f3565b5f81126103fd5760a08601938451156103ab575b50508351905f821361036c575b50506040519284518452516020840152604084015193600685101561035857606067ffffffffffffffff9160c09660408701520151166060840152511515608083015251151560a0820152f35b634e487b7160e01b5f52602160045260245ffd5b610377905191611526565b11610383575f8061030b565b7f2e3b1ec0000000000000000000000000000000000000000000000000000000005f5260045ffd5b6103c36103c9915160a06060820151910151906114e6565b91611526565b036103d5575f806102fe565b7f8f9003ee000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fae0bb491000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610298565b8091505160068110156103585760021490610291565b809150516006811015610358576001149061028a565b6003810361055657505090506104ca611498565b92805160068110156103585715908115610540575b811561052a575b8115610515575b501561044d575f8112156104255782526020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f6104ed565b80915051600681101561035857600214906104e6565b80915051600681101561035857600114906104df565b8061061f5750509050610567611498565b92805160068110156103585715908115610609575b81156105f3575b81156105de575b501561044d576104255760a0835101516105b6576020820152600160408201525f6060820152926102bc565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61058a565b8091505160068110156103585760021490610583565b809150516006811015610358576001149061057c565b600181036107185750509050610633611498565b928051600681101561035857600114908115610702575b81156106ed575b501561044d5761066c845160a06060820151910151906114e6565b8651106106c55761068a82610685836106858a516115a7565b6114f3565b5f81126103fd5761069f60a0865101516115a7565b136103fd5782526020820152600360408201525f6060820152600160a0820152926102bc565b7f7fa0800f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610651565b809150516006811015610358576002149061064a565b6004810361083857505061072a611498565b938051600681101561035857600114908115610822575b811561080d575b501561044d57610425576080016060815101519081156107e55761077a855160ff604060a0830151920151169061161e565b61078c60ff604084510151168461161e565b036105b65760806107a091510151916115a7565b036107bd576020820152600160408201525f6060820152926102bc565b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610748565b8091505160068110156103585760021490610741565b909391929060058103610a83575061084e611498565b948051600681101561035857600114908115610a6d575b8115610a58575b501561044d5761087f60208551016114d9565b600a81101561035857600403610a305767ffffffffffffffff81511667ffffffffffffffff6108b1818751511661155b565b1603610a0857608001916060835101516107e55760a0835101516105b65760a0865101516105b657610425576109e05760606080835101510151906080815101516108fb836115a7565b036107bd575160c00151610916610911836115a7565b61157b565b036109b8576060845101519060608084510151015182039182116109a45760ff6040608061094e61095b9584848b510151169061161e565b955101510151169061161e565b0361097c575f81525f6020820152600160408201525f6060820152926102bc565b7f733d14c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd916ea0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f7dcd8ffd000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61086c565b8091505160068110156103585760021490610865565b9193909160068103610b3f57505090610a9a611498565b938051600681101561035857600114908115610b29575b8115610b14575b501561044d576104255760a0845101516105b6576080016080815101516107bd576060815101516107e55760c0610af360a0835101516115a7565b91510151036109b8576020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f610ab8565b8091505160068110156103585760021490610ab1565b60078103610bd957505090610b52611498565b938051600681101561035857600114908115610bc3575b8115610bae575b501561044d576104255760a0845101516105b6576080016060815101516107e55760a0815101516105b657516107a060c0608083015192015161157b565b9050516006811015610358576004145f610b70565b8091505160068110156103585760021490610b69565b60088103610e1557505090610bec611498565b938051600681101561035857158015610e01575b15610ce5575050608001805160600151915081156107e55760a0815101516105b6576060845101516107e557610c44845160ff604060a0830151920151169061161e565b610c5660ff604084510151168461161e565b03610cbd57610c8d9060ff6040610c82610c7c8851848460c0830151920151169061165b565b956115a7565b92510151169061165b565b036109b8576080825101516107bd57610caa60a0835101516115a7565b6020820152600460408201525b926102bc565b7f7b208b9d000000000000000000000000000000000000000000000000000000005f5260045ffd5b8051600681101561035857600114908115610dec575b501561044d574667ffffffffffffffff8651511603610dc457610425576060845101519081156107e55760a0855101516105b657608001906060825101516107e557610d55825160ff604060a0830151920151169061161e565b610d6760ff604088510151168361161e565b03610cbd57610d9f610d90610d8a845160ff604060c0830151920151169061165b565b926115a7565b60ff604088510151169061165b565b036109b85751608001516107bd576020820152600160408201525f6060820152610cb7565b7f67525583000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576002145f610cfb565b508051600681101561035857600514610c00565b600903610f5757610e24611498565b948051600681101561035857600403610ed957504667ffffffffffffffff8751511603610dc457610e5860208251016114d9565b600a81101561035857600803610a305767ffffffffffffffff82511667ffffffffffffffff610e8a818451511661155b565b1603610a0857606080915101510151606086510151036107e55760a0855101516105b6576080016060815101516107e5575160a001516105b657610425576109e05760016040820152926102bc565b919250508051600681101561035857600114908115610f42575b501561044d576060845101516107e55760a0845101516105b657608001606081510151156107e5575160a001516105b6576020820152600560408201525f6060820152600160a0820152610cb7565b9050516006811015610358576002145f610ef3565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b5167ffffffffffffffff164211610f96575f610219565b7ff06506c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff019de0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f114a9df4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f26c21ae4000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff60808401515116156101c7577f4c7b586e000000000000000000000000000000000000000000000000000000005f5260045ffd5b508351600a81101561035857600914610199565b508351600a81101561035857600814610192565b508351600a8110156103585760071461018b565b508351600a81101561035857600614610184565b508351600a8110156103585760051461017d565b6020015173ffffffffffffffffffffffffffffffffffffffff168061115b575060ff601291511603611133575b5f80610161565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f92816111f7575b506111c2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461112c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011611231575b81611213602093836112c9565b8101031261122d575160ff8116810361122d57915f611195565b5f80fd5b3d9150611206565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b60608101515167ffffffffffffffff1615915081611281575b505f610135565b67ffffffffffffffff9150608001515116155f61127a565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff82111761129957604052565b90601f601f19910116810190811067ffffffffffffffff82111761129957604052565b359067ffffffffffffffff8216820361122d57565b91908260e091031261122d57604051611319816112ad565b8092611324816112ec565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361122d576020830152604081013560ff8116810361122d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561122d5780359067ffffffffffffffff821161129957604051926113c06020601f19601f86011601856112c9565b8284526020838301011161122d57815f926020809301838601378301015290565b91906102608382031261122d57604051906113fb826112ad565b8193611406816112ec565b83526020810135600a81101561122d576020840152604081013560408401526114328260608301611301565b6060840152611445826101408301611301565b608084015261022081013567ffffffffffffffff811161122d578261146b91830161138b565b60a08401526102408101359167ffffffffffffffff831161122d5760c092611493920161138b565b910152565b6040519060c0820182811067ffffffffffffffff821117611299576040525f60a0838281528260208201528260408201528260608201528260808201520152565b51600a8110156103585790565b919082018092116109a457565b9190915f83820193841291129080158216911516176109a457565b81810392915f1380158285131691841216176109a457565b5f81126115305790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116109a457565b7f800000000000000000000000000000000000000000000000000000000000000081146109a4575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116115d15790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116109a457565b60ff16604d81116109a457600a0a90565b9060ff811660128111611239576012146116575761163e611643916115fc565b61160d565b908181029181830414901517156109a45790565b5090565b9060ff811660128111611239576012146116575761163e61167b916115fc565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166109a45781830514901517156109a4579056fea264697066735822122036f6b0f3261f4d84fa391cd2e29d848110238f6d49d373a5912f2304cae9c86d64736f6c634300081e0033", + "nonce": "0x28", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x9712dcbc9f46d075bb90ab9d5cbbdf30195810bb050150b302cb3aaaf0e71bc0", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x124792", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610edc908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e714610118576324063eba1461002e575f80fd5b60206003193601126101145760043567ffffffffffffffff81116101145761005a903690600401610c2a565b610062610ced565b90516004811015610100575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610ca2565b0390f35b634e487b7160e01b5f52601160045260245ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60406003193601126101145760043567ffffffffffffffff811161011457610144903690600401610c2a565b60243567ffffffffffffffff811161011457610164903690600401610b73565b61016c610ced565b5081516004811015610100576003146109dc5767ffffffffffffffff461660608201908067ffffffffffffffff83515116146109b457608083019067ffffffffffffffff825151160361098c5767ffffffffffffffff835116156107a25780516040810190601260ff83511611610964574667ffffffffffffffff8251161461082e575b5050805160a0606082015191015181018091116100c45761021c825160c0608082015191015190610d17565b5f81126108065761022c90610d5e565b036107de57610239610ced565b5060208301928351600a811015610100576006810361052657505061025c610ced565b9184516004811015610100576104fe576060825101516104d6576080825101516104ae5781519160c060a084015193015161029684610d93565b03610486576102c360ff60406102b88551838360608301519201511690610e0a565b935101511684610e0a565b1161045e575160a00151610436576102da90610d93565b60208201526001604082015260016080820152915b825115801590610429575b15610401578251906103126020850192835190610d17565b928051600a81101561010057600603610366575050510361033e576100c0905b60405191829182610ca2565b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092915051600a81101561010057600714610387575b50506100c090610332565b8251036103d95760406103a261039d8451610d32565b610d5e565b910151036103b157818061037c565b7fd9132288000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b50602083015115156102fa565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f06b4cdae000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b90929060070361077a57610538610ced565b92855160048110156101005760011480156107ca575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff16036107a257602081510151600a811015610100577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0161077a5760a06080825101510151926060815101516104d6576080815101516105f36105ee86610d93565b610d32565b036104ae5760a081510151610436575160c0015161061084610d93565b036107025760608251015160608083510151015111156107525760608082510151015160608351015181039081116100c4576106559060ff6040855101511690610e0a565b61066b60ff604060808551015101511685610e0a565b0361072a5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561070257604060806106cb6106c56106d89660ff856106ba8298610d32565b925101511690610e47565b96610d93565b9351015101511690610e47565b03610486576106ed6105ee6040850151610d93565b8152600360408201525f6080820152916102ef565b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fffda345d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f25e3e1b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156101005760021461054e565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061088a575060ff601291511603610862575b84806101f0565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610926575b506108f1577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461085b577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d60201161095c575b8161094260209383610a50565b81010312610114575160ff811681036101145791876108c4565b3d9150610935565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff821117610a2057604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff821117610a2057604052565b90601f601f19910116810190811067ffffffffffffffff821117610a2057604052565b359067ffffffffffffffff8216820361011457565b359073ffffffffffffffffffffffffffffffffffffffff8216820361011457565b91908260e091031261011457604051610ac181610a04565b8092610acc81610a73565b8252610ada60208201610a88565b6020830152604081013560ff811681036101145760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156101145780359067ffffffffffffffff8211610a205760405192610b526020601f19601f8601160185610a50565b8284526020838301011161011457815f926020809301838601378301015290565b9190610260838203126101145760405190610b8d82610a04565b8193610b9881610a73565b83526020810135600a81101561011457602084015260408101356040840152610bc48260608301610aa9565b6060840152610bd7826101408301610aa9565b608084015261022081013567ffffffffffffffff81116101145782610bfd918301610b1d565b60a08401526102408101359167ffffffffffffffff83116101145760c092610c259201610b1d565b910152565b91909160a0818403126101145760405190610c4482610a34565b81938135600481101561011457835260208201359067ffffffffffffffff82116101145782610c7c60809492610c2594869401610b73565b602086015260408101356040860152610c9760608201610a73565b606086015201610a88565b91909160a0810192805182526020810151602083015260408101516004811015610100576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610cfa82610a34565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b7f800000000000000000000000000000000000000000000000000000000000000081146100c4575f0390565b5f8112610d685790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610dbd5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff81166012811161096457601214610e4357610e2a610e2f91610de8565b610df9565b908181029181830414901517156100c45790565b5090565b9060ff81166012811161096457601214610e4357610e2a610e6791610de8565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166100c45781830514901517156100c4579056fea264697066735822122073585d1c2949228993d38506ffc5f542f9ffb1c023c1893a2f5522e50227b27564736f6c634300081e0033", + "nonce": "0x29", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e81a9f20bb7b3370a15b6402271a9f8e7eae63184e733c80273c497a4187983", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x11ad7a", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610e55908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146109095763bbc42f341461002f575f80fd5b604060031936011261085d5760043567ffffffffffffffff811161085d5761005b903690600401610bf4565b60243567ffffffffffffffff811161085d5761007b903690600401610b3d565b610083610cd9565b5081516004811015610343576003146108e15767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146108b957608082019067ffffffffffffffff82515116036108915767ffffffffffffffff8251161561067b5780516040810190601260ff83511611610869574667ffffffffffffffff8251161461072f575b5050805160a06060820151910151810180911161038c57610134825160c0608082015191015190610d09565b5f81126107075761014490610d50565b036106df57610151610cd9565b5060208201928351600a81101561034357600481036104685750909150610176610cd9565b918451600481101561034357610440578051916080606084015193015161019c84610d85565b036104185760a0825101516103f05760c0825101516103c85760ff60406101d26101dd9351838360a08301519201511690610dda565b935101511683610dda565b036103a0576101eb90610d85565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161038c5767ffffffffffffffff166060820152600160a0820152915b82511580159061037f575b1561035757825161024c6020850191825190610d09565b928051600a811015610343576004036102a65750505081510361027e5761027a905b60405191829182610c7a565b0390f35b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290919251600a811015610343576005146102c8575b50505061027a9061026e565b81510361031b576102e36102de60409251610d24565b610d50565b910151036102f3575f80806102bc565b7fb09443e7000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5060208301511515610235565b634e487b7160e01b5f52601160045260245ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f76ac27ca000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b60050361065357610477610cd9565b92855160048110156103435760011480156106cb575b156106a35767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161038c5767ffffffffffffffff160361067b57602083510151600a811015610343576003190161065357606060808451015101519060808151015161050383610d85565b036104185760c08151015161051f61051a84610d85565b610d24565b036103c85760608151015161062b575160a001516103f057606082510151606080855101510151810390811161038c576105656105799160ff6040865101511690610dda565b9160ff604060808751015101511690610dda565b036106035760a0815101516103f057606060808092510151925101510151908181035f831282808312821692139015161761038c57036105db576105c361051a6040850151610d85565b6020820152600360408201525f60a08201529161022a565b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fff0edb30000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156103435760021461048d565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061078b575060ff601291511603610763575b5f80610108565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610827575b506107f2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461075c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011610861575b8161084360209383610a25565b8101031261085d575160ff8116810361085d57915f6107c5565b5f80fd5b3d9150610836565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b602060031936011261085d5760043567ffffffffffffffff811161085d57610935903690600401610bf4565b61093d610cd9565b9080516004811015610343575f19016106a3576060015167ffffffffffffffff164210156109b157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161038c5767ffffffffffffffff61027a921660808201525f60a082015260405191829182610c7a565b7f2b39d042000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff8211176109f557604052565b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176109f557604052565b90601f601f19910116810190811067ffffffffffffffff8211176109f557604052565b359067ffffffffffffffff8216820361085d57565b91908260e091031261085d57604051610a75816109d9565b8092610a8081610a48565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361085d576020830152604081013560ff8116810361085d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561085d5780359067ffffffffffffffff82116109f55760405192610b1c6020601f19601f8601160185610a25565b8284526020838301011161085d57815f926020809301838601378301015290565b91906102608382031261085d5760405190610b57826109d9565b8193610b6281610a48565b83526020810135600a81101561085d57602084015260408101356040840152610b8e8260608301610a5d565b6060840152610ba1826101408301610a5d565b608084015261022081013567ffffffffffffffff811161085d5782610bc7918301610ae7565b60a08401526102408101359167ffffffffffffffff831161085d5760c092610bef9201610ae7565b910152565b91909160c08184031261085d5760405190610c0e82610a09565b81938135600481101561085d57835260208201359167ffffffffffffffff831161085d57610c4260a0939284938301610b3d565b602085015260408101356040850152610c5d60608201610a48565b6060850152610c6e60808201610a48565b60808501520135910152565b91909160c08101928051825260208101516020830152604081015160048110156103435760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b60405190610ce682610a09565b5f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761038c57565b7f8000000000000000000000000000000000000000000000000000000000000000811461038c575f0390565b5f8112610d5a5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610daf5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9060ff16601281116108695760128114610e1b5760120360ff811161038c5760ff16604d811161038c57600a0a9081810291818304149015171561038c5790565b509056fea2646970667358221220fc0a93f7abd0c8aae0f4edd1fab1eef03232af831542ee9ea9f3dcf8d76c3da064736f6c634300081e0033", + "nonce": "0x2a", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5e58e1f709d9ded21112c24523733b843486bf6ae775ffd10d86118a5c947cfe", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x880d5", + "value": "0x0", + "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", + "nonce": "0x2b", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83", + "function": null, + "arguments": [ + "0x735EB1026aFbA78B602dA39C6B59EABa95753686" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x6a626e", + "value": "0x0", + "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e0033000000000000000000000000735eb1026afba78b602da39c6b59eaba95753686", + "nonce": "0x2c", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x9fc18a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x3df2187dc8a50ef62abfeb377318888493042770315492070c4708584dfbf572", + "transactionIndex": "0x66", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x1410b0", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xacfd78", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x9712dcbc9f46d075bb90ab9d5cbbdf30195810bb050150b302cb3aaaf0e71bc0", + "transactionIndex": "0x67", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0xd3bee", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xb9c9d6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e81a9f20bb7b3370a15b6402271a9f8e7eae63184e733c80273c497a4187983", + "transactionIndex": "0x68", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0xccc5e", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xc05453", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x5e58e1f709d9ded21112c24523733b843486bf6ae775ffd10d86118a5c947cfe", + "transactionIndex": "0x69", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x68a7d", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x11229e3", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "transactionIndex": "0x6a", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x51d590", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + ], + "pending": [], + "returns": {}, + "timestamp": 1772892720931, + "chain": 11155111, + "commit": "fd394085" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json new file mode 100644 index 000000000..5af57197a --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/11155111/run-latest.json @@ -0,0 +1,188 @@ +{ + "transactions": [ + { + "hash": "0x3df2187dc8a50ef62abfeb377318888493042770315492070c4708584dfbf572", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1bb70c", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576116f0908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b604060031936011261122d5760043567ffffffffffffffff811161122d5760a0600319823603011261122d5760a0820182811067ffffffffffffffff821117611299576040528060040135600681101561122d578252602481013567ffffffffffffffff811161122d5761009f90600436918401016113e1565b602083019081526040830192604483013584526100c96084606083019460648101358652016112ec565b6080820190815260243567ffffffffffffffff811161122d576100f09036906004016113e1565b6100f8611498565b50606081019367ffffffffffffffff855151164603610dc45767ffffffffffffffff82511681519067ffffffffffffffff82511610908115611261575b5015610a085784516040810190601260ff83511611611239574667ffffffffffffffff825116146110ff575b505060208201928351600a8110156103585760041480156110eb575b80156110d7575b80156110c3575b80156110af575b801561109b575b1561105e576080830167ffffffffffffffff815151161561103657515167ffffffffffffffff16461461100e575b6101dc865160a06060820151910151906114e6565b6101f1875160c06080820151910151906114f3565b5f8112610fe65761020190611526565b03610fbe578451600681101561035857600214610f7f575b50610222611498565b5061023c608086510151608060608451015101519061150e565b9061025660c08751015160c060608451015101519061150e565b9351600a81101561035857600281036104b65750509050610275611498565b928051600681101561035857159081156104a0575b811561048a575b8115610475575b501561044d575f8113156104255782526020820152600160408201525f6060820152925b6102d96102d1608086019260018452516115a7565b8551906114f3565b926102ea60208601948551906114f3565b5f81126103fd5760a08601938451156103ab575b50508351905f821361036c575b50506040519284518452516020840152604084015193600685101561035857606067ffffffffffffffff9160c09660408701520151166060840152511515608083015251151560a0820152f35b634e487b7160e01b5f52602160045260245ffd5b610377905191611526565b11610383575f8061030b565b7f2e3b1ec0000000000000000000000000000000000000000000000000000000005f5260045ffd5b6103c36103c9915160a06060820151910151906114e6565b91611526565b036103d5575f806102fe565b7f8f9003ee000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fae0bb491000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610298565b8091505160068110156103585760021490610291565b809150516006811015610358576001149061028a565b6003810361055657505090506104ca611498565b92805160068110156103585715908115610540575b811561052a575b8115610515575b501561044d575f8112156104255782526020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f6104ed565b80915051600681101561035857600214906104e6565b80915051600681101561035857600114906104df565b8061061f5750509050610567611498565b92805160068110156103585715908115610609575b81156105f3575b81156105de575b501561044d576104255760a0835101516105b6576020820152600160408201525f6060820152926102bc565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61058a565b8091505160068110156103585760021490610583565b809150516006811015610358576001149061057c565b600181036107185750509050610633611498565b928051600681101561035857600114908115610702575b81156106ed575b501561044d5761066c845160a06060820151910151906114e6565b8651106106c55761068a82610685836106858a516115a7565b6114f3565b5f81126103fd5761069f60a0865101516115a7565b136103fd5782526020820152600360408201525f6060820152600160a0820152926102bc565b7f7fa0800f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610651565b809150516006811015610358576002149061064a565b6004810361083857505061072a611498565b938051600681101561035857600114908115610822575b811561080d575b501561044d57610425576080016060815101519081156107e55761077a855160ff604060a0830151920151169061161e565b61078c60ff604084510151168461161e565b036105b65760806107a091510151916115a7565b036107bd576020820152600160408201525f6060820152926102bc565b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610748565b8091505160068110156103585760021490610741565b909391929060058103610a83575061084e611498565b948051600681101561035857600114908115610a6d575b8115610a58575b501561044d5761087f60208551016114d9565b600a81101561035857600403610a305767ffffffffffffffff81511667ffffffffffffffff6108b1818751511661155b565b1603610a0857608001916060835101516107e55760a0835101516105b65760a0865101516105b657610425576109e05760606080835101510151906080815101516108fb836115a7565b036107bd575160c00151610916610911836115a7565b61157b565b036109b8576060845101519060608084510151015182039182116109a45760ff6040608061094e61095b9584848b510151169061161e565b955101510151169061161e565b0361097c575f81525f6020820152600160408201525f6060820152926102bc565b7f733d14c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd916ea0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f7dcd8ffd000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61086c565b8091505160068110156103585760021490610865565b9193909160068103610b3f57505090610a9a611498565b938051600681101561035857600114908115610b29575b8115610b14575b501561044d576104255760a0845101516105b6576080016080815101516107bd576060815101516107e55760c0610af360a0835101516115a7565b91510151036109b8576020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f610ab8565b8091505160068110156103585760021490610ab1565b60078103610bd957505090610b52611498565b938051600681101561035857600114908115610bc3575b8115610bae575b501561044d576104255760a0845101516105b6576080016060815101516107e55760a0815101516105b657516107a060c0608083015192015161157b565b9050516006811015610358576004145f610b70565b8091505160068110156103585760021490610b69565b60088103610e1557505090610bec611498565b938051600681101561035857158015610e01575b15610ce5575050608001805160600151915081156107e55760a0815101516105b6576060845101516107e557610c44845160ff604060a0830151920151169061161e565b610c5660ff604084510151168461161e565b03610cbd57610c8d9060ff6040610c82610c7c8851848460c0830151920151169061165b565b956115a7565b92510151169061165b565b036109b8576080825101516107bd57610caa60a0835101516115a7565b6020820152600460408201525b926102bc565b7f7b208b9d000000000000000000000000000000000000000000000000000000005f5260045ffd5b8051600681101561035857600114908115610dec575b501561044d574667ffffffffffffffff8651511603610dc457610425576060845101519081156107e55760a0855101516105b657608001906060825101516107e557610d55825160ff604060a0830151920151169061161e565b610d6760ff604088510151168361161e565b03610cbd57610d9f610d90610d8a845160ff604060c0830151920151169061165b565b926115a7565b60ff604088510151169061165b565b036109b85751608001516107bd576020820152600160408201525f6060820152610cb7565b7f67525583000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576002145f610cfb565b508051600681101561035857600514610c00565b600903610f5757610e24611498565b948051600681101561035857600403610ed957504667ffffffffffffffff8751511603610dc457610e5860208251016114d9565b600a81101561035857600803610a305767ffffffffffffffff82511667ffffffffffffffff610e8a818451511661155b565b1603610a0857606080915101510151606086510151036107e55760a0855101516105b6576080016060815101516107e5575160a001516105b657610425576109e05760016040820152926102bc565b919250508051600681101561035857600114908115610f42575b501561044d576060845101516107e55760a0845101516105b657608001606081510151156107e5575160a001516105b6576020820152600560408201525f6060820152600160a0820152610cb7565b9050516006811015610358576002145f610ef3565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b5167ffffffffffffffff164211610f96575f610219565b7ff06506c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff019de0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f114a9df4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f26c21ae4000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff60808401515116156101c7577f4c7b586e000000000000000000000000000000000000000000000000000000005f5260045ffd5b508351600a81101561035857600914610199565b508351600a81101561035857600814610192565b508351600a8110156103585760071461018b565b508351600a81101561035857600614610184565b508351600a8110156103585760051461017d565b6020015173ffffffffffffffffffffffffffffffffffffffff168061115b575060ff601291511603611133575b5f80610161565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f92816111f7575b506111c2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461112c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011611231575b81611213602093836112c9565b8101031261122d575160ff8116810361122d57915f611195565b5f80fd5b3d9150611206565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b60608101515167ffffffffffffffff1615915081611281575b505f610135565b67ffffffffffffffff9150608001515116155f61127a565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff82111761129957604052565b90601f601f19910116810190811067ffffffffffffffff82111761129957604052565b359067ffffffffffffffff8216820361122d57565b91908260e091031261122d57604051611319816112ad565b8092611324816112ec565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361122d576020830152604081013560ff8116810361122d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561122d5780359067ffffffffffffffff821161129957604051926113c06020601f19601f86011601856112c9565b8284526020838301011161122d57815f926020809301838601378301015290565b91906102608382031261122d57604051906113fb826112ad565b8193611406816112ec565b83526020810135600a81101561122d576020840152604081013560408401526114328260608301611301565b6060840152611445826101408301611301565b608084015261022081013567ffffffffffffffff811161122d578261146b91830161138b565b60a08401526102408101359167ffffffffffffffff831161122d5760c092611493920161138b565b910152565b6040519060c0820182811067ffffffffffffffff821117611299576040525f60a0838281528260208201528260408201528260608201528260808201520152565b51600a8110156103585790565b919082018092116109a457565b9190915f83820193841291129080158216911516176109a457565b81810392915f1380158285131691841216176109a457565b5f81126115305790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116109a457565b7f800000000000000000000000000000000000000000000000000000000000000081146109a4575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116115d15790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116109a457565b60ff16604d81116109a457600a0a90565b9060ff811660128111611239576012146116575761163e611643916115fc565b61160d565b908181029181830414901517156109a45790565b5090565b9060ff811660128111611239576012146116575761163e61167b916115fc565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166109a45781830514901517156109a4579056fea264697066735822122036f6b0f3261f4d84fa391cd2e29d848110238f6d49d373a5912f2304cae9c86d64736f6c634300081e0033", + "nonce": "0x28", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x9712dcbc9f46d075bb90ab9d5cbbdf30195810bb050150b302cb3aaaf0e71bc0", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x124792", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610edc908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e714610118576324063eba1461002e575f80fd5b60206003193601126101145760043567ffffffffffffffff81116101145761005a903690600401610c2a565b610062610ced565b90516004811015610100575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610ca2565b0390f35b634e487b7160e01b5f52601160045260245ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60406003193601126101145760043567ffffffffffffffff811161011457610144903690600401610c2a565b60243567ffffffffffffffff811161011457610164903690600401610b73565b61016c610ced565b5081516004811015610100576003146109dc5767ffffffffffffffff461660608201908067ffffffffffffffff83515116146109b457608083019067ffffffffffffffff825151160361098c5767ffffffffffffffff835116156107a25780516040810190601260ff83511611610964574667ffffffffffffffff8251161461082e575b5050805160a0606082015191015181018091116100c45761021c825160c0608082015191015190610d17565b5f81126108065761022c90610d5e565b036107de57610239610ced565b5060208301928351600a811015610100576006810361052657505061025c610ced565b9184516004811015610100576104fe576060825101516104d6576080825101516104ae5781519160c060a084015193015161029684610d93565b03610486576102c360ff60406102b88551838360608301519201511690610e0a565b935101511684610e0a565b1161045e575160a00151610436576102da90610d93565b60208201526001604082015260016080820152915b825115801590610429575b15610401578251906103126020850192835190610d17565b928051600a81101561010057600603610366575050510361033e576100c0905b60405191829182610ca2565b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092915051600a81101561010057600714610387575b50506100c090610332565b8251036103d95760406103a261039d8451610d32565b610d5e565b910151036103b157818061037c565b7fd9132288000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b50602083015115156102fa565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f06b4cdae000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b90929060070361077a57610538610ced565b92855160048110156101005760011480156107ca575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff16036107a257602081510151600a811015610100577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0161077a5760a06080825101510151926060815101516104d6576080815101516105f36105ee86610d93565b610d32565b036104ae5760a081510151610436575160c0015161061084610d93565b036107025760608251015160608083510151015111156107525760608082510151015160608351015181039081116100c4576106559060ff6040855101511690610e0a565b61066b60ff604060808551015101511685610e0a565b0361072a5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561070257604060806106cb6106c56106d89660ff856106ba8298610d32565b925101511690610e47565b96610d93565b9351015101511690610e47565b03610486576106ed6105ee6040850151610d93565b8152600360408201525f6080820152916102ef565b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fffda345d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f25e3e1b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156101005760021461054e565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061088a575060ff601291511603610862575b84806101f0565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610926575b506108f1577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461085b577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d60201161095c575b8161094260209383610a50565b81010312610114575160ff811681036101145791876108c4565b3d9150610935565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff821117610a2057604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff821117610a2057604052565b90601f601f19910116810190811067ffffffffffffffff821117610a2057604052565b359067ffffffffffffffff8216820361011457565b359073ffffffffffffffffffffffffffffffffffffffff8216820361011457565b91908260e091031261011457604051610ac181610a04565b8092610acc81610a73565b8252610ada60208201610a88565b6020830152604081013560ff811681036101145760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156101145780359067ffffffffffffffff8211610a205760405192610b526020601f19601f8601160185610a50565b8284526020838301011161011457815f926020809301838601378301015290565b9190610260838203126101145760405190610b8d82610a04565b8193610b9881610a73565b83526020810135600a81101561011457602084015260408101356040840152610bc48260608301610aa9565b6060840152610bd7826101408301610aa9565b608084015261022081013567ffffffffffffffff81116101145782610bfd918301610b1d565b60a08401526102408101359167ffffffffffffffff83116101145760c092610c259201610b1d565b910152565b91909160a0818403126101145760405190610c4482610a34565b81938135600481101561011457835260208201359067ffffffffffffffff82116101145782610c7c60809492610c2594869401610b73565b602086015260408101356040860152610c9760608201610a73565b606086015201610a88565b91909160a0810192805182526020810151602083015260408101516004811015610100576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610cfa82610a34565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b7f800000000000000000000000000000000000000000000000000000000000000081146100c4575f0390565b5f8112610d685790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610dbd5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff81166012811161096457601214610e4357610e2a610e2f91610de8565b610df9565b908181029181830414901517156100c45790565b5090565b9060ff81166012811161096457601214610e4357610e2a610e6791610de8565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166100c45781830514901517156100c4579056fea264697066735822122073585d1c2949228993d38506ffc5f542f9ffb1c023c1893a2f5522e50227b27564736f6c634300081e0033", + "nonce": "0x29", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e81a9f20bb7b3370a15b6402271a9f8e7eae63184e733c80273c497a4187983", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x11ad7a", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610e55908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146109095763bbc42f341461002f575f80fd5b604060031936011261085d5760043567ffffffffffffffff811161085d5761005b903690600401610bf4565b60243567ffffffffffffffff811161085d5761007b903690600401610b3d565b610083610cd9565b5081516004811015610343576003146108e15767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146108b957608082019067ffffffffffffffff82515116036108915767ffffffffffffffff8251161561067b5780516040810190601260ff83511611610869574667ffffffffffffffff8251161461072f575b5050805160a06060820151910151810180911161038c57610134825160c0608082015191015190610d09565b5f81126107075761014490610d50565b036106df57610151610cd9565b5060208201928351600a81101561034357600481036104685750909150610176610cd9565b918451600481101561034357610440578051916080606084015193015161019c84610d85565b036104185760a0825101516103f05760c0825101516103c85760ff60406101d26101dd9351838360a08301519201511690610dda565b935101511683610dda565b036103a0576101eb90610d85565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161038c5767ffffffffffffffff166060820152600160a0820152915b82511580159061037f575b1561035757825161024c6020850191825190610d09565b928051600a811015610343576004036102a65750505081510361027e5761027a905b60405191829182610c7a565b0390f35b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290919251600a811015610343576005146102c8575b50505061027a9061026e565b81510361031b576102e36102de60409251610d24565b610d50565b910151036102f3575f80806102bc565b7fb09443e7000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5060208301511515610235565b634e487b7160e01b5f52601160045260245ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f76ac27ca000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b60050361065357610477610cd9565b92855160048110156103435760011480156106cb575b156106a35767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161038c5767ffffffffffffffff160361067b57602083510151600a811015610343576003190161065357606060808451015101519060808151015161050383610d85565b036104185760c08151015161051f61051a84610d85565b610d24565b036103c85760608151015161062b575160a001516103f057606082510151606080855101510151810390811161038c576105656105799160ff6040865101511690610dda565b9160ff604060808751015101511690610dda565b036106035760a0815101516103f057606060808092510151925101510151908181035f831282808312821692139015161761038c57036105db576105c361051a6040850151610d85565b6020820152600360408201525f60a08201529161022a565b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fff0edb30000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156103435760021461048d565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061078b575060ff601291511603610763575b5f80610108565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610827575b506107f2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461075c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011610861575b8161084360209383610a25565b8101031261085d575160ff8116810361085d57915f6107c5565b5f80fd5b3d9150610836565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b602060031936011261085d5760043567ffffffffffffffff811161085d57610935903690600401610bf4565b61093d610cd9565b9080516004811015610343575f19016106a3576060015167ffffffffffffffff164210156109b157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161038c5767ffffffffffffffff61027a921660808201525f60a082015260405191829182610c7a565b7f2b39d042000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff8211176109f557604052565b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176109f557604052565b90601f601f19910116810190811067ffffffffffffffff8211176109f557604052565b359067ffffffffffffffff8216820361085d57565b91908260e091031261085d57604051610a75816109d9565b8092610a8081610a48565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361085d576020830152604081013560ff8116810361085d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561085d5780359067ffffffffffffffff82116109f55760405192610b1c6020601f19601f8601160185610a25565b8284526020838301011161085d57815f926020809301838601378301015290565b91906102608382031261085d5760405190610b57826109d9565b8193610b6281610a48565b83526020810135600a81101561085d57602084015260408101356040840152610b8e8260608301610a5d565b6060840152610ba1826101408301610a5d565b608084015261022081013567ffffffffffffffff811161085d5782610bc7918301610ae7565b60a08401526102408101359167ffffffffffffffff831161085d5760c092610bef9201610ae7565b910152565b91909160c08184031261085d5760405190610c0e82610a09565b81938135600481101561085d57835260208201359167ffffffffffffffff831161085d57610c4260a0939284938301610b3d565b602085015260408101356040850152610c5d60608201610a48565b6060850152610c6e60808201610a48565b60808501520135910152565b91909160c08101928051825260208101516020830152604081015160048110156103435760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b60405190610ce682610a09565b5f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761038c57565b7f8000000000000000000000000000000000000000000000000000000000000000811461038c575f0390565b5f8112610d5a5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610daf5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9060ff16601281116108695760128114610e1b5760120360ff811161038c5760ff16604d811161038c57600a0a9081810291818304149015171561038c5790565b509056fea2646970667358221220fc0a93f7abd0c8aae0f4edd1fab1eef03232af831542ee9ea9f3dcf8d76c3da064736f6c634300081e0033", + "nonce": "0x2a", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5e58e1f709d9ded21112c24523733b843486bf6ae775ffd10d86118a5c947cfe", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x880d5", + "value": "0x0", + "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", + "nonce": "0x2b", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83", + "function": null, + "arguments": [ + "0x735EB1026aFbA78B602dA39C6B59EABa95753686" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x6a626e", + "value": "0x0", + "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e0033000000000000000000000000735eb1026afba78b602da39c6b59eaba95753686", + "nonce": "0x2c", + "chainId": "0xaa36a7" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x9fc18a", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x3df2187dc8a50ef62abfeb377318888493042770315492070c4708584dfbf572", + "transactionIndex": "0x66", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x1410b0", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xacfd78", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x9712dcbc9f46d075bb90ab9d5cbbdf30195810bb050150b302cb3aaaf0e71bc0", + "transactionIndex": "0x67", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0xd3bee", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xb9c9d6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e81a9f20bb7b3370a15b6402271a9f8e7eae63184e733c80273c497a4187983", + "transactionIndex": "0x68", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0xccc5e", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0xc05453", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x5e58e1f709d9ded21112c24523733b843486bf6ae775ffd10d86118a5c947cfe", + "transactionIndex": "0x69", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x68a7d", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x735eb1026afba78b602da39c6b59eaba95753686" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x11229e3", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "transactionIndex": "0x6a", + "blockHash": "0x6e814235abb6e3aa00d957143ca4c04c16879792d6232a9d367b2d793b23c018", + "blockNumber": "0x9ebc3c", + "gasUsed": "0x51d590", + "effectiveGasPrice": "0x135b23", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0xb7be0e2007ddf320d680942cb9e008f986e74f83" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + ], + "pending": [], + "returns": {}, + "timestamp": 1772892720931, + "chain": 11155111, + "commit": "fd394085" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1772893715802.json b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1772893715802.json new file mode 100644 index 000000000..7434592c5 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-1772893715802.json @@ -0,0 +1,278 @@ +{ + "transactions": [ + { + "hash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1bb70c", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576116f0908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b604060031936011261122d5760043567ffffffffffffffff811161122d5760a0600319823603011261122d5760a0820182811067ffffffffffffffff821117611299576040528060040135600681101561122d578252602481013567ffffffffffffffff811161122d5761009f90600436918401016113e1565b602083019081526040830192604483013584526100c96084606083019460648101358652016112ec565b6080820190815260243567ffffffffffffffff811161122d576100f09036906004016113e1565b6100f8611498565b50606081019367ffffffffffffffff855151164603610dc45767ffffffffffffffff82511681519067ffffffffffffffff82511610908115611261575b5015610a085784516040810190601260ff83511611611239574667ffffffffffffffff825116146110ff575b505060208201928351600a8110156103585760041480156110eb575b80156110d7575b80156110c3575b80156110af575b801561109b575b1561105e576080830167ffffffffffffffff815151161561103657515167ffffffffffffffff16461461100e575b6101dc865160a06060820151910151906114e6565b6101f1875160c06080820151910151906114f3565b5f8112610fe65761020190611526565b03610fbe578451600681101561035857600214610f7f575b50610222611498565b5061023c608086510151608060608451015101519061150e565b9061025660c08751015160c060608451015101519061150e565b9351600a81101561035857600281036104b65750509050610275611498565b928051600681101561035857159081156104a0575b811561048a575b8115610475575b501561044d575f8113156104255782526020820152600160408201525f6060820152925b6102d96102d1608086019260018452516115a7565b8551906114f3565b926102ea60208601948551906114f3565b5f81126103fd5760a08601938451156103ab575b50508351905f821361036c575b50506040519284518452516020840152604084015193600685101561035857606067ffffffffffffffff9160c09660408701520151166060840152511515608083015251151560a0820152f35b634e487b7160e01b5f52602160045260245ffd5b610377905191611526565b11610383575f8061030b565b7f2e3b1ec0000000000000000000000000000000000000000000000000000000005f5260045ffd5b6103c36103c9915160a06060820151910151906114e6565b91611526565b036103d5575f806102fe565b7f8f9003ee000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fae0bb491000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610298565b8091505160068110156103585760021490610291565b809150516006811015610358576001149061028a565b6003810361055657505090506104ca611498565b92805160068110156103585715908115610540575b811561052a575b8115610515575b501561044d575f8112156104255782526020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f6104ed565b80915051600681101561035857600214906104e6565b80915051600681101561035857600114906104df565b8061061f5750509050610567611498565b92805160068110156103585715908115610609575b81156105f3575b81156105de575b501561044d576104255760a0835101516105b6576020820152600160408201525f6060820152926102bc565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61058a565b8091505160068110156103585760021490610583565b809150516006811015610358576001149061057c565b600181036107185750509050610633611498565b928051600681101561035857600114908115610702575b81156106ed575b501561044d5761066c845160a06060820151910151906114e6565b8651106106c55761068a82610685836106858a516115a7565b6114f3565b5f81126103fd5761069f60a0865101516115a7565b136103fd5782526020820152600360408201525f6060820152600160a0820152926102bc565b7f7fa0800f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610651565b809150516006811015610358576002149061064a565b6004810361083857505061072a611498565b938051600681101561035857600114908115610822575b811561080d575b501561044d57610425576080016060815101519081156107e55761077a855160ff604060a0830151920151169061161e565b61078c60ff604084510151168461161e565b036105b65760806107a091510151916115a7565b036107bd576020820152600160408201525f6060820152926102bc565b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610748565b8091505160068110156103585760021490610741565b909391929060058103610a83575061084e611498565b948051600681101561035857600114908115610a6d575b8115610a58575b501561044d5761087f60208551016114d9565b600a81101561035857600403610a305767ffffffffffffffff81511667ffffffffffffffff6108b1818751511661155b565b1603610a0857608001916060835101516107e55760a0835101516105b65760a0865101516105b657610425576109e05760606080835101510151906080815101516108fb836115a7565b036107bd575160c00151610916610911836115a7565b61157b565b036109b8576060845101519060608084510151015182039182116109a45760ff6040608061094e61095b9584848b510151169061161e565b955101510151169061161e565b0361097c575f81525f6020820152600160408201525f6060820152926102bc565b7f733d14c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd916ea0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f7dcd8ffd000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61086c565b8091505160068110156103585760021490610865565b9193909160068103610b3f57505090610a9a611498565b938051600681101561035857600114908115610b29575b8115610b14575b501561044d576104255760a0845101516105b6576080016080815101516107bd576060815101516107e55760c0610af360a0835101516115a7565b91510151036109b8576020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f610ab8565b8091505160068110156103585760021490610ab1565b60078103610bd957505090610b52611498565b938051600681101561035857600114908115610bc3575b8115610bae575b501561044d576104255760a0845101516105b6576080016060815101516107e55760a0815101516105b657516107a060c0608083015192015161157b565b9050516006811015610358576004145f610b70565b8091505160068110156103585760021490610b69565b60088103610e1557505090610bec611498565b938051600681101561035857158015610e01575b15610ce5575050608001805160600151915081156107e55760a0815101516105b6576060845101516107e557610c44845160ff604060a0830151920151169061161e565b610c5660ff604084510151168461161e565b03610cbd57610c8d9060ff6040610c82610c7c8851848460c0830151920151169061165b565b956115a7565b92510151169061165b565b036109b8576080825101516107bd57610caa60a0835101516115a7565b6020820152600460408201525b926102bc565b7f7b208b9d000000000000000000000000000000000000000000000000000000005f5260045ffd5b8051600681101561035857600114908115610dec575b501561044d574667ffffffffffffffff8651511603610dc457610425576060845101519081156107e55760a0855101516105b657608001906060825101516107e557610d55825160ff604060a0830151920151169061161e565b610d6760ff604088510151168361161e565b03610cbd57610d9f610d90610d8a845160ff604060c0830151920151169061165b565b926115a7565b60ff604088510151169061165b565b036109b85751608001516107bd576020820152600160408201525f6060820152610cb7565b7f67525583000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576002145f610cfb565b508051600681101561035857600514610c00565b600903610f5757610e24611498565b948051600681101561035857600403610ed957504667ffffffffffffffff8751511603610dc457610e5860208251016114d9565b600a81101561035857600803610a305767ffffffffffffffff82511667ffffffffffffffff610e8a818451511661155b565b1603610a0857606080915101510151606086510151036107e55760a0855101516105b6576080016060815101516107e5575160a001516105b657610425576109e05760016040820152926102bc565b919250508051600681101561035857600114908115610f42575b501561044d576060845101516107e55760a0845101516105b657608001606081510151156107e5575160a001516105b6576020820152600560408201525f6060820152600160a0820152610cb7565b9050516006811015610358576002145f610ef3565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b5167ffffffffffffffff164211610f96575f610219565b7ff06506c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff019de0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f114a9df4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f26c21ae4000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff60808401515116156101c7577f4c7b586e000000000000000000000000000000000000000000000000000000005f5260045ffd5b508351600a81101561035857600914610199565b508351600a81101561035857600814610192565b508351600a8110156103585760071461018b565b508351600a81101561035857600614610184565b508351600a8110156103585760051461017d565b6020015173ffffffffffffffffffffffffffffffffffffffff168061115b575060ff601291511603611133575b5f80610161565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f92816111f7575b506111c2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461112c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011611231575b81611213602093836112c9565b8101031261122d575160ff8116810361122d57915f611195565b5f80fd5b3d9150611206565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b60608101515167ffffffffffffffff1615915081611281575b505f610135565b67ffffffffffffffff9150608001515116155f61127a565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff82111761129957604052565b90601f601f19910116810190811067ffffffffffffffff82111761129957604052565b359067ffffffffffffffff8216820361122d57565b91908260e091031261122d57604051611319816112ad565b8092611324816112ec565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361122d576020830152604081013560ff8116810361122d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561122d5780359067ffffffffffffffff821161129957604051926113c06020601f19601f86011601856112c9565b8284526020838301011161122d57815f926020809301838601378301015290565b91906102608382031261122d57604051906113fb826112ad565b8193611406816112ec565b83526020810135600a81101561122d576020840152604081013560408401526114328260608301611301565b6060840152611445826101408301611301565b608084015261022081013567ffffffffffffffff811161122d578261146b91830161138b565b60a08401526102408101359167ffffffffffffffff831161122d5760c092611493920161138b565b910152565b6040519060c0820182811067ffffffffffffffff821117611299576040525f60a0838281528260208201528260408201528260608201528260808201520152565b51600a8110156103585790565b919082018092116109a457565b9190915f83820193841291129080158216911516176109a457565b81810392915f1380158285131691841216176109a457565b5f81126115305790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116109a457565b7f800000000000000000000000000000000000000000000000000000000000000081146109a4575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116115d15790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116109a457565b60ff16604d81116109a457600a0a90565b9060ff811660128111611239576012146116575761163e611643916115fc565b61160d565b908181029181830414901517156109a45790565b5090565b9060ff811660128111611239576012146116575761163e61167b916115fc565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166109a45781830514901517156109a4579056fea264697066735822122036f6b0f3261f4d84fa391cd2e29d848110238f6d49d373a5912f2304cae9c86d64736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x124792", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610edc908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e714610118576324063eba1461002e575f80fd5b60206003193601126101145760043567ffffffffffffffff81116101145761005a903690600401610c2a565b610062610ced565b90516004811015610100575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610ca2565b0390f35b634e487b7160e01b5f52601160045260245ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60406003193601126101145760043567ffffffffffffffff811161011457610144903690600401610c2a565b60243567ffffffffffffffff811161011457610164903690600401610b73565b61016c610ced565b5081516004811015610100576003146109dc5767ffffffffffffffff461660608201908067ffffffffffffffff83515116146109b457608083019067ffffffffffffffff825151160361098c5767ffffffffffffffff835116156107a25780516040810190601260ff83511611610964574667ffffffffffffffff8251161461082e575b5050805160a0606082015191015181018091116100c45761021c825160c0608082015191015190610d17565b5f81126108065761022c90610d5e565b036107de57610239610ced565b5060208301928351600a811015610100576006810361052657505061025c610ced565b9184516004811015610100576104fe576060825101516104d6576080825101516104ae5781519160c060a084015193015161029684610d93565b03610486576102c360ff60406102b88551838360608301519201511690610e0a565b935101511684610e0a565b1161045e575160a00151610436576102da90610d93565b60208201526001604082015260016080820152915b825115801590610429575b15610401578251906103126020850192835190610d17565b928051600a81101561010057600603610366575050510361033e576100c0905b60405191829182610ca2565b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092915051600a81101561010057600714610387575b50506100c090610332565b8251036103d95760406103a261039d8451610d32565b610d5e565b910151036103b157818061037c565b7fd9132288000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b50602083015115156102fa565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f06b4cdae000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b90929060070361077a57610538610ced565b92855160048110156101005760011480156107ca575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff16036107a257602081510151600a811015610100577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0161077a5760a06080825101510151926060815101516104d6576080815101516105f36105ee86610d93565b610d32565b036104ae5760a081510151610436575160c0015161061084610d93565b036107025760608251015160608083510151015111156107525760608082510151015160608351015181039081116100c4576106559060ff6040855101511690610e0a565b61066b60ff604060808551015101511685610e0a565b0361072a5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561070257604060806106cb6106c56106d89660ff856106ba8298610d32565b925101511690610e47565b96610d93565b9351015101511690610e47565b03610486576106ed6105ee6040850151610d93565b8152600360408201525f6080820152916102ef565b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fffda345d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f25e3e1b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156101005760021461054e565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061088a575060ff601291511603610862575b84806101f0565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610926575b506108f1577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461085b577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d60201161095c575b8161094260209383610a50565b81010312610114575160ff811681036101145791876108c4565b3d9150610935565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff821117610a2057604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff821117610a2057604052565b90601f601f19910116810190811067ffffffffffffffff821117610a2057604052565b359067ffffffffffffffff8216820361011457565b359073ffffffffffffffffffffffffffffffffffffffff8216820361011457565b91908260e091031261011457604051610ac181610a04565b8092610acc81610a73565b8252610ada60208201610a88565b6020830152604081013560ff811681036101145760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156101145780359067ffffffffffffffff8211610a205760405192610b526020601f19601f8601160185610a50565b8284526020838301011161011457815f926020809301838601378301015290565b9190610260838203126101145760405190610b8d82610a04565b8193610b9881610a73565b83526020810135600a81101561011457602084015260408101356040840152610bc48260608301610aa9565b6060840152610bd7826101408301610aa9565b608084015261022081013567ffffffffffffffff81116101145782610bfd918301610b1d565b60a08401526102408101359167ffffffffffffffff83116101145760c092610c259201610b1d565b910152565b91909160a0818403126101145760405190610c4482610a34565b81938135600481101561011457835260208201359067ffffffffffffffff82116101145782610c7c60809492610c2594869401610b73565b602086015260408101356040860152610c9760608201610a73565b606086015201610a88565b91909160a0810192805182526020810151602083015260408101516004811015610100576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610cfa82610a34565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b7f800000000000000000000000000000000000000000000000000000000000000081146100c4575f0390565b5f8112610d685790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610dbd5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff81166012811161096457601214610e4357610e2a610e2f91610de8565b610df9565b908181029181830414901517156100c45790565b5090565b9060ff81166012811161096457601214610e4357610e2a610e6791610de8565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166100c45781830514901517156100c4579056fea264697066735822122073585d1c2949228993d38506ffc5f542f9ffb1c023c1893a2f5522e50227b27564736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x11ad7a", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610e55908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146109095763bbc42f341461002f575f80fd5b604060031936011261085d5760043567ffffffffffffffff811161085d5761005b903690600401610bf4565b60243567ffffffffffffffff811161085d5761007b903690600401610b3d565b610083610cd9565b5081516004811015610343576003146108e15767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146108b957608082019067ffffffffffffffff82515116036108915767ffffffffffffffff8251161561067b5780516040810190601260ff83511611610869574667ffffffffffffffff8251161461072f575b5050805160a06060820151910151810180911161038c57610134825160c0608082015191015190610d09565b5f81126107075761014490610d50565b036106df57610151610cd9565b5060208201928351600a81101561034357600481036104685750909150610176610cd9565b918451600481101561034357610440578051916080606084015193015161019c84610d85565b036104185760a0825101516103f05760c0825101516103c85760ff60406101d26101dd9351838360a08301519201511690610dda565b935101511683610dda565b036103a0576101eb90610d85565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161038c5767ffffffffffffffff166060820152600160a0820152915b82511580159061037f575b1561035757825161024c6020850191825190610d09565b928051600a811015610343576004036102a65750505081510361027e5761027a905b60405191829182610c7a565b0390f35b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290919251600a811015610343576005146102c8575b50505061027a9061026e565b81510361031b576102e36102de60409251610d24565b610d50565b910151036102f3575f80806102bc565b7fb09443e7000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5060208301511515610235565b634e487b7160e01b5f52601160045260245ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f76ac27ca000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b60050361065357610477610cd9565b92855160048110156103435760011480156106cb575b156106a35767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161038c5767ffffffffffffffff160361067b57602083510151600a811015610343576003190161065357606060808451015101519060808151015161050383610d85565b036104185760c08151015161051f61051a84610d85565b610d24565b036103c85760608151015161062b575160a001516103f057606082510151606080855101510151810390811161038c576105656105799160ff6040865101511690610dda565b9160ff604060808751015101511690610dda565b036106035760a0815101516103f057606060808092510151925101510151908181035f831282808312821692139015161761038c57036105db576105c361051a6040850151610d85565b6020820152600360408201525f60a08201529161022a565b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fff0edb30000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156103435760021461048d565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061078b575060ff601291511603610763575b5f80610108565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610827575b506107f2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461075c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011610861575b8161084360209383610a25565b8101031261085d575160ff8116810361085d57915f6107c5565b5f80fd5b3d9150610836565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b602060031936011261085d5760043567ffffffffffffffff811161085d57610935903690600401610bf4565b61093d610cd9565b9080516004811015610343575f19016106a3576060015167ffffffffffffffff164210156109b157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161038c5767ffffffffffffffff61027a921660808201525f60a082015260405191829182610c7a565b7f2b39d042000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff8211176109f557604052565b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176109f557604052565b90601f601f19910116810190811067ffffffffffffffff8211176109f557604052565b359067ffffffffffffffff8216820361085d57565b91908260e091031261085d57604051610a75816109d9565b8092610a8081610a48565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361085d576020830152604081013560ff8116810361085d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561085d5780359067ffffffffffffffff82116109f55760405192610b1c6020601f19601f8601160185610a25565b8284526020838301011161085d57815f926020809301838601378301015290565b91906102608382031261085d5760405190610b57826109d9565b8193610b6281610a48565b83526020810135600a81101561085d57602084015260408101356040840152610b8e8260608301610a5d565b6060840152610ba1826101408301610a5d565b608084015261022081013567ffffffffffffffff811161085d5782610bc7918301610ae7565b60a08401526102408101359167ffffffffffffffff831161085d5760c092610bef9201610ae7565b910152565b91909160c08184031261085d5760405190610c0e82610a09565b81938135600481101561085d57835260208201359167ffffffffffffffff831161085d57610c4260a0939284938301610b3d565b602085015260408101356040850152610c5d60608201610a48565b6060850152610c6e60808201610a48565b60808501520135910152565b91909160c08101928051825260208101516020830152604081015160048110156103435760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b60405190610ce682610a09565b5f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761038c57565b7f8000000000000000000000000000000000000000000000000000000000000000811461038c575f0390565b5f8112610d5a5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610daf5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9060ff16601281116108695760128114610e1b5760120360ff811161038c5760ff16604d811161038c57600a0a9081810291818304149015171561038c5790565b509056fea2646970667358221220fc0a93f7abd0c8aae0f4edd1fab1eef03232af831542ee9ea9f3dcf8d76c3da064736f6c634300081e0033", + "nonce": "0x4", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x880d5", + "value": "0x0", + "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", + "nonce": "0x5", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d", + "function": null, + "arguments": [ + "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x6a626e", + "value": "0x0", + "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e00330000000000000000000000002a35728cadd8076dfd424fc3e20974a3cd03bfa5", + "nonce": "0x6", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x1410b0", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000008e12cca7c1e150000000000000000000000000000000000000000000000000a467417de9a33bd1000000000000000000000000000000000000000000002b297badcd163bced3e7000000000000000000000000000000000000000000000000a3d92eb141e15a81000000000000000000000000000000000000000000002b297c3bdfe2e390b537", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionIndex": "0x0", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x1410b0", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x214c9e", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000005db48e1b848b52000000000000000000000000000000000000000000000000a3d92eb13cf13f31000000000000000000000000000000000000000000002b297c3bdfe2e390b537000000000000000000000000000000000000000000000000a37b7a23216cb3df000000000000000000000000000000000000000000002b297c999470ff154089", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionIndex": "0x1", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0xd3bee", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x2e18fc", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000005a9ea056b694e2000000000000000000000000000000000000000000000000a37b7a231e2af44d000000000000000000000000000000000000000000002b297c999470ff154089000000000000000000000000000000000000000000000000a320db82c7745f6b000000000000000000000000000000000000000000002b297cf4331155cbd56b", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionIndex": "0x2", + "logIndex": "0x2", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionIndex": "0x2", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0xccc5e", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x34a379", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000002e505f361a7163000000000000000000000000000000000000000000000000a320db82c44e1449000000000000000000000000000000000000000000002b297cf4331155cbd56b000000000000000000000000000000000000000000000000a2f28b238e33a2e6000000000000000000000000000000000000000000002b297d2283708be646ce", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionIndex": "0x3", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionIndex": "0x3", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x68a7d", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x867909", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000002436f597d48d070000000000000000000000000000000000000000000000000a2f28b238c978e23000000000000000000000000000000000000000000002b297d2283708be646ce000000000000000000000000000000000000000000000000a0af1bca0f4ebdb3000000000000000000000000000000000000000000002b297f65f2ca092f173e", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionIndex": "0x4", + "logIndex": "0x4", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionIndex": "0x4", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x51d590", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + ], + "pending": [], + "returns": {}, + "timestamp": 1772893715802, + "chain": 80002, + "commit": "4b4244f1" +} \ No newline at end of file diff --git a/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json new file mode 100644 index 000000000..7434592c5 --- /dev/null +++ b/contracts/broadcast/DeployChannelHub.s.sol/80002/run-latest.json @@ -0,0 +1,278 @@ +{ + "transactions": [ + { + "hash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionType": "CREATE2", + "contractName": "ChannelEngine.channelhub", + "contractAddress": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x1bb70c", + "input": "0x0000000000000000000000000000000000000000000000000000000000000000608080604052346019576116f0908161001e823930815050f35b5f80fdfe6080806040526004361015610012575f80fd5b5f3560e01c63a8b4483c14610025575f80fd5b604060031936011261122d5760043567ffffffffffffffff811161122d5760a0600319823603011261122d5760a0820182811067ffffffffffffffff821117611299576040528060040135600681101561122d578252602481013567ffffffffffffffff811161122d5761009f90600436918401016113e1565b602083019081526040830192604483013584526100c96084606083019460648101358652016112ec565b6080820190815260243567ffffffffffffffff811161122d576100f09036906004016113e1565b6100f8611498565b50606081019367ffffffffffffffff855151164603610dc45767ffffffffffffffff82511681519067ffffffffffffffff82511610908115611261575b5015610a085784516040810190601260ff83511611611239574667ffffffffffffffff825116146110ff575b505060208201928351600a8110156103585760041480156110eb575b80156110d7575b80156110c3575b80156110af575b801561109b575b1561105e576080830167ffffffffffffffff815151161561103657515167ffffffffffffffff16461461100e575b6101dc865160a06060820151910151906114e6565b6101f1875160c06080820151910151906114f3565b5f8112610fe65761020190611526565b03610fbe578451600681101561035857600214610f7f575b50610222611498565b5061023c608086510151608060608451015101519061150e565b9061025660c08751015160c060608451015101519061150e565b9351600a81101561035857600281036104b65750509050610275611498565b928051600681101561035857159081156104a0575b811561048a575b8115610475575b501561044d575f8113156104255782526020820152600160408201525f6060820152925b6102d96102d1608086019260018452516115a7565b8551906114f3565b926102ea60208601948551906114f3565b5f81126103fd5760a08601938451156103ab575b50508351905f821361036c575b50506040519284518452516020840152604084015193600685101561035857606067ffffffffffffffff9160c09660408701520151166060840152511515608083015251151560a0820152f35b634e487b7160e01b5f52602160045260245ffd5b610377905191611526565b11610383575f8061030b565b7f2e3b1ec0000000000000000000000000000000000000000000000000000000005f5260045ffd5b6103c36103c9915160a06060820151910151906114e6565b91611526565b036103d5575f806102fe565b7f8f9003ee000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fae0bb491000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610298565b8091505160068110156103585760021490610291565b809150516006811015610358576001149061028a565b6003810361055657505090506104ca611498565b92805160068110156103585715908115610540575b811561052a575b8115610515575b501561044d575f8112156104255782526020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f6104ed565b80915051600681101561035857600214906104e6565b80915051600681101561035857600114906104df565b8061061f5750509050610567611498565b92805160068110156103585715908115610609575b81156105f3575b81156105de575b501561044d576104255760a0835101516105b6576020820152600160408201525f6060820152926102bc565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61058a565b8091505160068110156103585760021490610583565b809150516006811015610358576001149061057c565b600181036107185750509050610633611498565b928051600681101561035857600114908115610702575b81156106ed575b501561044d5761066c845160a06060820151910151906114e6565b8651106106c55761068a82610685836106858a516115a7565b6114f3565b5f81126103fd5761069f60a0865101516115a7565b136103fd5782526020820152600360408201525f6060820152600160a0820152926102bc565b7f7fa0800f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610651565b809150516006811015610358576002149061064a565b6004810361083857505061072a611498565b938051600681101561035857600114908115610822575b811561080d575b501561044d57610425576080016060815101519081156107e55761077a855160ff604060a0830151920151169061161e565b61078c60ff604084510151168461161e565b036105b65760806107a091510151916115a7565b036107bd576020820152600160408201525f6060820152926102bc565b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f610748565b8091505160068110156103585760021490610741565b909391929060058103610a83575061084e611498565b948051600681101561035857600114908115610a6d575b8115610a58575b501561044d5761087f60208551016114d9565b600a81101561035857600403610a305767ffffffffffffffff81511667ffffffffffffffff6108b1818751511661155b565b1603610a0857608001916060835101516107e55760a0835101516105b65760a0865101516105b657610425576109e05760606080835101510151906080815101516108fb836115a7565b036107bd575160c00151610916610911836115a7565b61157b565b036109b8576060845101519060608084510151015182039182116109a45760ff6040608061094e61095b9584848b510151169061161e565b955101510151169061161e565b0361097c575f81525f6020820152600160408201525f6060820152926102bc565b7f733d14c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fd916ea0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f7dcd8ffd000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576004145f61086c565b8091505160068110156103585760021490610865565b9193909160068103610b3f57505090610a9a611498565b938051600681101561035857600114908115610b29575b8115610b14575b501561044d576104255760a0845101516105b6576080016080815101516107bd576060815101516107e55760c0610af360a0835101516115a7565b91510151036109b8576020820152600160408201525f6060820152926102bc565b9050516006811015610358576004145f610ab8565b8091505160068110156103585760021490610ab1565b60078103610bd957505090610b52611498565b938051600681101561035857600114908115610bc3575b8115610bae575b501561044d576104255760a0845101516105b6576080016060815101516107e55760a0815101516105b657516107a060c0608083015192015161157b565b9050516006811015610358576004145f610b70565b8091505160068110156103585760021490610b69565b60088103610e1557505090610bec611498565b938051600681101561035857158015610e01575b15610ce5575050608001805160600151915081156107e55760a0815101516105b6576060845101516107e557610c44845160ff604060a0830151920151169061161e565b610c5660ff604084510151168461161e565b03610cbd57610c8d9060ff6040610c82610c7c8851848460c0830151920151169061165b565b956115a7565b92510151169061165b565b036109b8576080825101516107bd57610caa60a0835101516115a7565b6020820152600460408201525b926102bc565b7f7b208b9d000000000000000000000000000000000000000000000000000000005f5260045ffd5b8051600681101561035857600114908115610dec575b501561044d574667ffffffffffffffff8651511603610dc457610425576060845101519081156107e55760a0855101516105b657608001906060825101516107e557610d55825160ff604060a0830151920151169061161e565b610d6760ff604088510151168361161e565b03610cbd57610d9f610d90610d8a845160ff604060c0830151920151169061165b565b926115a7565b60ff604088510151169061165b565b036109b85751608001516107bd576020820152600160408201525f6060820152610cb7565b7f67525583000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050516006811015610358576002145f610cfb565b508051600681101561035857600514610c00565b600903610f5757610e24611498565b948051600681101561035857600403610ed957504667ffffffffffffffff8751511603610dc457610e5860208251016114d9565b600a81101561035857600803610a305767ffffffffffffffff82511667ffffffffffffffff610e8a818451511661155b565b1603610a0857606080915101510151606086510151036107e55760a0855101516105b6576080016060815101516107e5575160a001516105b657610425576109e05760016040820152926102bc565b919250508051600681101561035857600114908115610f42575b501561044d576060845101516107e55760a0845101516105b657608001606081510151156107e5575160a001516105b6576020820152600560408201525f6060820152600160a0820152610cb7565b9050516006811015610358576002145f610ef3565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b5167ffffffffffffffff164211610f96575f610219565b7ff06506c5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff019de0e000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f114a9df4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f26c21ae4000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff60808401515116156101c7577f4c7b586e000000000000000000000000000000000000000000000000000000005f5260045ffd5b508351600a81101561035857600914610199565b508351600a81101561035857600814610192565b508351600a8110156103585760071461018b565b508351600a81101561035857600614610184565b508351600a8110156103585760051461017d565b6020015173ffffffffffffffffffffffffffffffffffffffff168061115b575060ff601291511603611133575b5f80610161565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f92816111f7575b506111c2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461112c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011611231575b81611213602093836112c9565b8101031261122d575160ff8116810361122d57915f611195565b5f80fd5b3d9150611206565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b60608101515167ffffffffffffffff1615915081611281575b505f610135565b67ffffffffffffffff9150608001515116155f61127a565b634e487b7160e01b5f52604160045260245ffd5b60e0810190811067ffffffffffffffff82111761129957604052565b90601f601f19910116810190811067ffffffffffffffff82111761129957604052565b359067ffffffffffffffff8216820361122d57565b91908260e091031261122d57604051611319816112ad565b8092611324816112ec565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361122d576020830152604081013560ff8116810361122d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561122d5780359067ffffffffffffffff821161129957604051926113c06020601f19601f86011601856112c9565b8284526020838301011161122d57815f926020809301838601378301015290565b91906102608382031261122d57604051906113fb826112ad565b8193611406816112ec565b83526020810135600a81101561122d576020840152604081013560408401526114328260608301611301565b6060840152611445826101408301611301565b608084015261022081013567ffffffffffffffff811161122d578261146b91830161138b565b60a08401526102408101359167ffffffffffffffff831161122d5760c092611493920161138b565b910152565b6040519060c0820182811067ffffffffffffffff821117611299576040525f60a0838281528260208201528260408201528260608201528260808201520152565b51600a8110156103585790565b919082018092116109a457565b9190915f83820193841291129080158216911516176109a457565b81810392915f1380158285131691841216176109a457565b5f81126115305790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b67ffffffffffffffff60019116019067ffffffffffffffff82116109a457565b7f800000000000000000000000000000000000000000000000000000000000000081146109a4575f0390565b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81116115d15790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116109a457565b60ff16604d81116109a457600a0a90565b9060ff811660128111611239576012146116575761163e611643916115fc565b61160d565b908181029181830414901517156109a45790565b5090565b9060ff811660128111611239576012146116575761163e61167b916115fc565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166109a45781830514901517156109a4579056fea264697066735822122036f6b0f3261f4d84fa391cd2e29d848110238f6d49d373a5912f2304cae9c86d64736f6c634300081e0033", + "nonce": "0x2", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionType": "CREATE2", + "contractName": "EscrowWithdrawalEngine.channelhub", + "contractAddress": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x124792", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610edc908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8062ea54e714610118576324063eba1461002e575f80fd5b60206003193601126101145760043567ffffffffffffffff81116101145761005a903690600401610c2a565b610062610ced565b90516004811015610100575f19016100d857600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff82116100c45767ffffffffffffffff6100c0921660608201525f608082015260405191829182610ca2565b0390f35b634e487b7160e01b5f52601160045260245ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b5f80fd5b60406003193601126101145760043567ffffffffffffffff811161011457610144903690600401610c2a565b60243567ffffffffffffffff811161011457610164903690600401610b73565b61016c610ced565b5081516004811015610100576003146109dc5767ffffffffffffffff461660608201908067ffffffffffffffff83515116146109b457608083019067ffffffffffffffff825151160361098c5767ffffffffffffffff835116156107a25780516040810190601260ff83511611610964574667ffffffffffffffff8251161461082e575b5050805160a0606082015191015181018091116100c45761021c825160c0608082015191015190610d17565b5f81126108065761022c90610d5e565b036107de57610239610ced565b5060208301928351600a811015610100576006810361052657505061025c610ced565b9184516004811015610100576104fe576060825101516104d6576080825101516104ae5781519160c060a084015193015161029684610d93565b03610486576102c360ff60406102b88551838360608301519201511690610e0a565b935101511684610e0a565b1161045e575160a00151610436576102da90610d93565b60208201526001604082015260016080820152915b825115801590610429575b15610401578251906103126020850192835190610d17565b928051600a81101561010057600603610366575050510361033e576100c0905b60405191829182610ca2565b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092915051600a81101561010057600714610387575b50506100c090610332565b8251036103d95760406103a261039d8451610d32565b610d5e565b910151036103b157818061037c565b7fd9132288000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b50602083015115156102fa565b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f06b4cdae000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c66f955000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b90929060070361077a57610538610ced565b92855160048110156101005760011480156107ca575b156100d85767ffffffffffffffff9051166020860190600167ffffffffffffffff835151160167ffffffffffffffff81116100c45767ffffffffffffffff16036107a257602081510151600a811015610100577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0161077a5760a06080825101510151926060815101516104d6576080815101516105f36105ee86610d93565b610d32565b036104ae5760a081510151610436575160c0015161061084610d93565b036107025760608251015160608083510151015111156107525760608082510151015160608351015181039081116100c4576106559060ff6040855101511690610e0a565b61066b60ff604060808551015101511685610e0a565b0361072a5760c08251015160c06060835101510151905f82820392128183128116918313901516176100c4575f81121561070257604060806106cb6106c56106d89660ff856106ba8298610d32565b925101511690610e47565b96610d93565b9351015101511690610e47565b03610486576106ed6105ee6040850151610d93565b8152600360408201525f6080820152916102ef565b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fffda345d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f25e3e1b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156101005760021461054e565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061088a575060ff601291511603610862575b84806101f0565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610926575b506108f1577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461085b577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d60201161095c575b8161094260209383610a50565b81010312610114575160ff811681036101145791876108c4565b3d9150610935565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff821117610a2057604052565b634e487b7160e01b5f52604160045260245ffd5b60a0810190811067ffffffffffffffff821117610a2057604052565b90601f601f19910116810190811067ffffffffffffffff821117610a2057604052565b359067ffffffffffffffff8216820361011457565b359073ffffffffffffffffffffffffffffffffffffffff8216820361011457565b91908260e091031261011457604051610ac181610a04565b8092610acc81610a73565b8252610ada60208201610a88565b6020830152604081013560ff811681036101145760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f820112156101145780359067ffffffffffffffff8211610a205760405192610b526020601f19601f8601160185610a50565b8284526020838301011161011457815f926020809301838601378301015290565b9190610260838203126101145760405190610b8d82610a04565b8193610b9881610a73565b83526020810135600a81101561011457602084015260408101356040840152610bc48260608301610aa9565b6060840152610bd7826101408301610aa9565b608084015261022081013567ffffffffffffffff81116101145782610bfd918301610b1d565b60a08401526102408101359167ffffffffffffffff83116101145760c092610c259201610b1d565b910152565b91909160a0818403126101145760405190610c4482610a34565b81938135600481101561011457835260208201359067ffffffffffffffff82116101145782610c7c60809492610c2594869401610b73565b602086015260408101356040860152610c9760608201610a73565b606086015201610a88565b91909160a0810192805182526020810151602083015260408101516004811015610100576080918291604085015267ffffffffffffffff606082015116606085015201511515910152565b60405190610cfa82610a34565b5f6080838281528260208201528260408201528260608201520152565b9190915f83820193841291129080158216911516176100c457565b7f800000000000000000000000000000000000000000000000000000000000000081146100c4575f0390565b5f8112610d685790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610dbd5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff166012039060ff82116100c457565b60ff16604d81116100c457600a0a90565b9060ff81166012811161096457601214610e4357610e2a610e2f91610de8565b610df9565b908181029181830414901517156100c45790565b5090565b9060ff81166012811161096457601214610e4357610e2a610e6791610de8565b90818102917f800000000000000000000000000000000000000000000000000000000000000081145f8312166100c45781830514901517156100c4579056fea264697066735822122073585d1c2949228993d38506ffc5f542f9ffb1c023c1893a2f5522e50227b27564736f6c634300081e0033", + "nonce": "0x3", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionType": "CREATE2", + "contractName": "EscrowDepositEngine.channelhub", + "contractAddress": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "gas": "0x11ad7a", + "input": "0x000000000000000000000000000000000000000000000000000000000000000060808060405234601957610e55908161001e823930815050f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80636666e4c0146109095763bbc42f341461002f575f80fd5b604060031936011261085d5760043567ffffffffffffffff811161085d5761005b903690600401610bf4565b60243567ffffffffffffffff811161085d5761007b903690600401610b3d565b610083610cd9565b5081516004811015610343576003146108e15767ffffffffffffffff46169060608101918067ffffffffffffffff84515116146108b957608082019067ffffffffffffffff82515116036108915767ffffffffffffffff8251161561067b5780516040810190601260ff83511611610869574667ffffffffffffffff8251161461072f575b5050805160a06060820151910151810180911161038c57610134825160c0608082015191015190610d09565b5f81126107075761014490610d50565b036106df57610151610cd9565b5060208201928351600a81101561034357600481036104685750909150610176610cd9565b918451600481101561034357610440578051916080606084015193015161019c84610d85565b036104185760a0825101516103f05760c0825101516103c85760ff60406101d26101dd9351838360a08301519201511690610dda565b935101511683610dda565b036103a0576101eb90610d85565b815260016040820152612a3067ffffffffffffffff42160167ffffffffffffffff811161038c5767ffffffffffffffff166060820152600160a0820152915b82511580159061037f575b1561035757825161024c6020850191825190610d09565b928051600a811015610343576004036102a65750505081510361027e5761027a905b60405191829182610c7a565b0390f35b7f8041118f000000000000000000000000000000000000000000000000000000005f5260045ffd5b9290919251600a811015610343576005146102c8575b50505061027a9061026e565b81510361031b576102e36102de60409251610d24565b610d50565b910151036102f3575f80806102bc565b7fb09443e7000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8b8380f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52602160045260245ffd5b7f17bc734e000000000000000000000000000000000000000000000000000000005f5260045ffd5b5060208301511515610235565b634e487b7160e01b5f52601160045260245ffd5b7fe19f88d5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f0c18740d000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa5eabfa5000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f76ac27ca000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fed778779000000000000000000000000000000000000000000000000000000005f5260045ffd5b60050361065357610477610cd9565b92855160048110156103435760011480156106cb575b156106a35767ffffffffffffffff905116916020860192600167ffffffffffffffff855151160167ffffffffffffffff811161038c5767ffffffffffffffff160361067b57602083510151600a811015610343576003190161065357606060808451015101519060808151015161050383610d85565b036104185760c08151015161051f61051a84610d85565b610d24565b036103c85760608151015161062b575160a001516103f057606082510151606080855101510151810390811161038c576105656105799160ff6040865101511690610dda565b9160ff604060808751015101511690610dda565b036106035760a0815101516103f057606060808092510151925101510151908181035f831282808312821692139015161761038c57036105db576105c361051a6040850151610d85565b6020820152600360408201525f60a08201529161022a565b7f1180da8f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fff0edb30000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f2c0a0276000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f07646e49000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f392ebf28000000000000000000000000000000000000000000000000000000005f5260045ffd5b50855160048110156103435760021461048d565b7f92ad5c75000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe6af4070000000000000000000000000000000000000000000000000000000005f5260045ffd5b6020015173ffffffffffffffffffffffffffffffffffffffff168061078b575060ff601291511603610763575b5f80610108565b7f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b906020600492604051938480927f313ce5670000000000000000000000000000000000000000000000000000000082525afa5f9281610827575b506107f2577f6afa2af9000000000000000000000000000000000000000000000000000000005f5260045ffd5b60ff8091511691161461075c577f5a8dbaed000000000000000000000000000000000000000000000000000000005f5260045ffd5b9092506020813d602011610861575b8161084360209383610a25565b8101031261085d575160ff8116810361085d57915f6107c5565b5f80fd5b3d9150610836565b7fb016c3f4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f9ba78e55000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f21e65f65000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f69155645000000000000000000000000000000000000000000000000000000005f5260045ffd5b602060031936011261085d5760043567ffffffffffffffff811161085d57610935903690600401610bf4565b61093d610cd9565b9080516004811015610343575f19016106a3576060015167ffffffffffffffff164210156109b157600260408201526201518067ffffffffffffffff4216019067ffffffffffffffff821161038c5767ffffffffffffffff61027a921660808201525f60a082015260405191829182610c7a565b7f2b39d042000000000000000000000000000000000000000000000000000000005f5260045ffd5b60e0810190811067ffffffffffffffff8211176109f557604052565b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176109f557604052565b90601f601f19910116810190811067ffffffffffffffff8211176109f557604052565b359067ffffffffffffffff8216820361085d57565b91908260e091031261085d57604051610a75816109d9565b8092610a8081610a48565b8252602081013573ffffffffffffffffffffffffffffffffffffffff8116810361085d576020830152604081013560ff8116810361085d5760c09182916040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b81601f8201121561085d5780359067ffffffffffffffff82116109f55760405192610b1c6020601f19601f8601160185610a25565b8284526020838301011161085d57815f926020809301838601378301015290565b91906102608382031261085d5760405190610b57826109d9565b8193610b6281610a48565b83526020810135600a81101561085d57602084015260408101356040840152610b8e8260608301610a5d565b6060840152610ba1826101408301610a5d565b608084015261022081013567ffffffffffffffff811161085d5782610bc7918301610ae7565b60a08401526102408101359167ffffffffffffffff831161085d5760c092610bef9201610ae7565b910152565b91909160c08184031261085d5760405190610c0e82610a09565b81938135600481101561085d57835260208201359167ffffffffffffffff831161085d57610c4260a0939284938301610b3d565b602085015260408101356040850152610c5d60608201610a48565b6060850152610c6e60808201610a48565b60808501520135910152565b91909160c08101928051825260208101516020830152604081015160048110156103435760a0918291604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff608082015116608085015201511515910152565b60405190610ce682610a09565b5f60a0838281528260208201528260408201528260608201528260808201520152565b9190915f838201938412911290801582169115161761038c57565b7f8000000000000000000000000000000000000000000000000000000000000000811461038c575f0390565b5f8112610d5a5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111610daf5790565b7f24775e06000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9060ff16601281116108695760128114610e1b5760120360ff811161038c5760ff16604d811161038c57600a0a9081810291818304149015171561038c5790565b509056fea2646970667358221220fc0a93f7abd0c8aae0f4edd1fab1eef03232af831542ee9ea9f3dcf8d76c3da064736f6c634300081e0033", + "nonce": "0x4", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionType": "CREATE", + "contractName": "ECDSAValidator.channelhub", + "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5", + "function": null, + "arguments": null, + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x880d5", + "value": "0x0", + "input": "0x608080604052346015576106d6908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c63600109bb14610024575f80fd5b346100cc5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100cc5760243567ffffffffffffffff81116100cc576100739036906004016100d0565b9060443567ffffffffffffffff81116100cc576100949036906004016100d0565b6064359173ffffffffffffffffffffffffffffffffffffffff831683036100cc576020946100c4946004356101a0565b604051908152f35b5f80fd5b9181601f840112156100cc5782359167ffffffffffffffff83116100cc57602083818601950101116100cc57565b90601f601f19910116810190811067ffffffffffffffff82111761012157604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b67ffffffffffffffff811161012157601f01601f191660200190565b9291926101768261014e565b9161018460405193846100fe565b8294818452818301116100cc578281602093845f960137010152565b929091949383156102635773ffffffffffffffffffffffffffffffffffffffff85161561023b5761022060806101de6102279561022d99369161016a565b95601f19601f6020604051998a94828601526040808601528051918291826060880152018686015e5f858286010152011681010301601f1981018652856100fe565b369161016a565b9061028b565b1561023757600190565b5f90565b7f4501a919000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fe1b97cf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91825192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156104cc575b806d04ee2d6d415b85acef8100000000600a9210156104b1575b662386f26fc1000081101561049d575b6305f5e10081101561048c575b61271081101561047d575b606481101561046f575b1015610465575b6001850190600a602161033461031e8561014e565b9461032c60405196876100fe565b80865261014e565b97601f19602086019901368a378401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530490811561038057600a90610345565b505073ffffffffffffffffffffffffffffffffffffffff5f9361040c86610415946020610404869b603a604051938492818401967f19457468657265756d205369676e6564204d6573736167653a0a00000000000088525180918486015e83018281019d8e528c8051928391019e8f905e01015f815203601f1981018352826100fe565b5190206104f4565b9094919461052e565b169416841461045c5773ffffffffffffffffffffffffffffffffffffffff9261044d92610444925190206104f4565b9092919261052e565b1614610457575f90565b600190565b50505050600190565b9360010193610309565b606460029104960195610302565b612710600491049601956102f8565b6305f5e100600891049601956102ed565b662386f26fc10000601091049601956102e0565b6d04ee2d6d415b85acef8100000000602091049601956102d0565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000081046102b6565b81519190604183036105245761051d9250602082015190606060408401519301515f1a90610606565b9192909190565b50505f9160029190565b60048110156105d95780610540575050565b60018103610570577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b600281036105a457507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003146105ae5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411610695579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa1561068a575f5173ffffffffffffffffffffffffffffffffffffffff81161561068057905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f916003919056fea2646970667358221220c8e32dfe4c3317faffb02d4b02fddbb5e01dbc789e117442dd5ec08557786de764736f6c634300081e0033", + "nonce": "0x5", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + }, + { + "hash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionType": "CREATE", + "contractName": "ChannelHub", + "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d", + "function": null, + "arguments": [ + "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5" + ], + "transaction": { + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "gas": "0x6a626e", + "value": "0x0", + "input": "0x60a0346100aa57601f61608238819003918201601f19168301916001600160401b038311848410176100ae578084926020946040528339810103126100aa57516001600160a01b0381168082036100aa5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00551561009b57608052604051615fbf90816100c382396080518181816111420152613f180152f35b63e6c4247b60e01b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c806316b390b11461024457806317536c061461023f578063187576d81461023a5780633115f6301461023557806338a66be21461023057806341b660ef1461022b57806347de477a146102265780635326919814610221578063587675e81461021c5780635a0745b4146102175780635b9acbf9146102125780635dc46a741461020d5780636840dbd2146102085780636898234b146102035780636af820bd146101fe57806371a47141146101f9578063735181f0146101f457806382d3e15d146101ef5780638d0b12a5146101ea57806394191051146101e55780639691b468146101e0578063a5c82680146101db578063b00b6fd6146101d6578063b25a1d38146101d1578063beed9d5f146101cc578063c74a2d10146101c7578063d888ccae146101c2578063dc23f29e146101bd578063dd73d494146101b8578063e617208c146101b3578063ecf3d7e8146101ae578063f4ac51f5146101a9578063f766f8d6146101a4578063ff5bc09e1461019f5763ffa1ad741461019a575f80fd5b612650565b612639565b61249a565b61241f565b61230d565b61226e565b6120f0565b611ef5565b611db5565b611b71565b6119d6565b6116c4565b61165b565b6114ba565b611379565b61135c565b6111cb565b6111ae565b611171565b61112d565b611112565b611026565b61100f565b610fc8565b610fa6565b610f8a565b610f44565b610cfc565b610b13565b610850565b6107ea565b61065e565b6105d8565b6104ca565b6102cb565b9181601f840112156102775782359167ffffffffffffffff8311610277576020838186019501011161027757565b5f80fd5b60643590600282101561027757565b90606060031983011261027757600435916024359067ffffffffffffffff8211610277576102ba91600401610249565b909160443560028110156102775790565b34610277576102d93661028a565b6103986102f1859493945f52600260205260405f2090565b9283546102ff81151561266b565b61035a600286019461032a61031b87546001600160a01b031690565b948560038a019a8b5492613eff565b9591600160068b019a019661034a88546001600160a01b039060081c1690565b926103548c6128a0565b8861405d565b60c061036588614161565b604051809581927f6666e4c000000000000000000000000000000000000000000000000000000000835260048301612a25565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577fba075bd445233f7cad862c72f0343b3503aad9c8e704a2295f122b82abf8e80196610436956080955f9461044b575b50836104146104066104279697546001600160a01b039060081c1690565b92546001600160a01b031690565b9254936104208a6128a0565b908c61428d565b015167ffffffffffffffff1690565b9061044660405192839283612a41565b0390a2005b6104279450946104146104786104069760c03d60c011610481575b6104708183612707565b810190612950565b955050946103e8565b503d610466565b612a36565b6001600160a01b0381160361027757565b6003196060910112610277576004356104b68161048d565b906024356104c38161048d565b9060443590565b6001600160a01b036104db3661049e565b92909116906104eb821515612b9e565b6104f6831515612bcd565b815f52600660205261051c8160405f20906001600160a01b03165f5260205260405f2090565b80549184830180931161059a577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7926001600160a01b03925561055d615810565b61056885823361458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556040519485521692602090a3005b612bfc565b60206040818301928281528451809452019201905f5b8181106105c25750505090565b82518452602093840193909201916001016105b5565b34610277576020600319360112610277576001600160a01b036004356105fd8161048d565b165f52600160205260405f206040519081602082549182815201915f5260205f20905f5b818110610648576106448561063881870382612707565b6040519182918261059f565b0390f35b8254845260209093019260019283019201610621565b3461027757602060031936011261027757600354600480549190355f5b828410806107e1575b156107d4576106b06106a2610698866131a0565b90549060031b1c90565b5f52600260205260405f2090565b6001810160036106c1825460ff1690565b6106ca81611c00565b146107c2576106d882615b4d565b1561077e57915f8261076961077595600561076f96019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b6001600160a01b03165f52600660205260405f2090565b92015460401c6001600160a01b031690565b6001600160a01b03165f5260205260405f2090565b918254612c10565b9055600360ff19825416179055565b556139c6565b936139c6565b915b919261067b565b505092905061078d9150600455565b8061079457005b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1005b5050926107ce906139c6565b91610777565b92905061078d9150600455565b50818110610684565b34610277575f600319360112610277576020604051620186a08152f35b90816102609103126102775790565b90600319820160e081126102775760c0136102775760049160c4359067ffffffffffffffff82116102775761084d91600401610807565b90565b61085936610816565b906020820191600261086a84612c27565b61087381611c0f565b148015610af8575b8015610ada575b61088b90612c31565b61091a6108a061089b3685612c79565b614780565b916108aa8461483e565b60208401906108b882612ced565b956108d760408701976108ca89612ced565b608089013591858961494b565b60c0826108ff6108f86108ec6107148c612ced565b61073d60808501612ced565b54886149c5565b6040519687928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af493841561048857610a156001600160a01b0394610a2d936109967fb00e209e275d0e1892f1982b34d3f545d1628aebd95322d7ce3585c558f638b498610a1b955f91610aab575b50610985368d612c79565b61098f368a61301c565b908c614b52565b6109c2896109bd6109a685612ced565b6001600160a01b03165f52600160205260405f2090565b615bde565b5060026109ce82612c27565b6109d781611c0f565b03610a325750877f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f4177869620669660405180610a0d89826130ca565b0390a2612ced565b97612ced565b918360405194859416981696836130db565b0390a4005b610a3d600391612c27565b610a4681611c0f565b03610a7b57877f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf98660405180610a0d89826130ca565b877f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc60405180610a0d89826130ca565b610acd915060c03d60c011610ad3575b610ac58183612707565b810190612cf7565b5f61097a565b503d610abb565b5061088b610ae784612c27565b610af081611c0f565b159050610882565b506003610b0484612c27565b610b0d81611c0f565b1461087b565b610b1c36610816565b90610b3d6004610b2e60208501612c27565b610b3781611c0f565b14612c31565b610b4a61089b3683612c79565b9160208201610b5881612ced565b90610b7960408501926080610b6c85612ced565b960135958691868961494b565b610b8b610b858461316b565b86614c6c565b93610b9586614c9c565b15610bdd57505050610bd881610bcc7f471c4ebe4e57d25ef7117e141caac31c6b98f067b8098a7a7bbd38f637c2f9809386614cf9565b604051918291826130ca565b0390a3005b610c259060c085610bf18897959697614161565b60405194859283927fbbc42f3400000000000000000000000000000000000000000000000000000000845260048401613175565b038173728904e52308213ba61c90ef49f34c18fbda9e115af48015610488577fede7867afa7cdb9c443667efd8244d98bf9df1dce68e60dc94dca6605125ca7695610bd895610c9a945f93610ca3575b50610c82610c8891612ced565b91612ced565b91610c93368761301c565b8a8a61428d565b610bcc846131ef565b610c88919350610cc4610c829160c03d60c011610481576104708183612707565b939150610c75565b90604060031983011261027757600435916024359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610d0a36610ccc565b610d1b6009610b2e60208401612c27565b610d376001610d31845f525f60205260405f2090565b0161323d565b610dfd610d4e60208301516001600160a01b031690565b9161071460c060408301610d7a610d6c82516001600160a01b031690565b608086015190888a8c61494b565b610de2610ddb610dc4610d8d368b61301c565b9586946101408c018d8d610da08361316b565b67ffffffffffffffff1646149d8e610eb7575b50505050516001600160a01b031690565b6060840151602001516001600160a01b031661073d565b54896149c5565b6040519586928392632a2d120f60e21b8452600484016132c9565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857610e2f935f93610e96575b5086614b52565b15610e65576104467f9a6f675cc94b83b55f1ecc0876affd4332a30c92e6faa2aca0199b1b6df922c391604051918291826130ca565b6104467f7b20773c41402791c5f18914dbbeacad38b1ebcc4c55d8eb3bfe0a4cde26c82691604051918291826130ca565b610eb091935060c03d60c011610ad357610ac58183612707565b915f610e28565b610edb610f1092610ecc610f15963690612f3c565b60608d01526060369101612f3c565b60808b0152610ee86132b5565b60a08b0152610ef56132b5565b8b8b01526001600160a01b03165f52600160205260405f2090565b615c88565b505f8d8d82610db3565b600319604091011261027757600435610f378161048d565b9060243561084d8161048d565b34610277576020610f816001600160a01b03610f5f36610f1f565b91165f526006835260405f20906001600160a01b03165f5260205260405f2090565b54604051908152f35b34610277575f600319360112610277576020604051612a308152f35b3461027757604060031936011261027757610644610638602435600435613373565b610fda610fd436610ccc565b9061342d565b005b60606003198201126102775760043591602435916044359067ffffffffffffffff82116102775761084d91600401610807565b3461027757610fda61102036610fdc565b9161378a565b34610277576020600319360112610277576001600160a01b0360043561104b8161048d565b165f52600160205261105f60405f20615b05565b5f905f5b81518110156110ff5761109161108a61107c838561335f565b515f525f60205260405f2090565b5460ff1690565b61109a816121c1565b600381141590816110ea575b506110b4575b600101611063565b916110c78184600193106110cf576139c6565b9290506110ac565b6110d9858561335f565b516110e4828661335f565b526139c6565b600591506110f7816121c1565b14155f6110a6565b506106449181526040519182918261059f565b34610277575f60031936011261027757602060405160408152f35b34610277575f600319360112610277576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610277576020610f816001600160a01b0361118c36610f1f565b91165f526008835260405f20906001600160a01b03165f5260205260405f2090565b34610277575f600319360112610277576020600454604051908152f35b34610277576112556111dc3661028a565b929391906111f2855f52600560205260405f2090565b9182549261120184151561266b565b600281019060a061122261121c84546001600160a01b031690565b8a615053565b604051809881927f24063eba000000000000000000000000000000000000000000000000000000008352600483016139d4565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4958615610488575f9661132b575b50600181015460081c6001600160a01b0316968792546112a2906001600160a01b031690565b809581956003850154976112b7928992613eff565b9a9190946006019a6112c88c6128a0565b956112d3968b61405d565b846112dd876128a0565b6112e795896150bb565b6060015167ffffffffffffffff166040519182916113059183612a41565b037fb8568a1f475f3c76759a620e08a653d28348c5c09e2e0bc91d533339801fefd891a2005b61134e91965060a03d60a011611355575b6113468183612707565b8101906136bc565b945f61127c565b503d61133c565b34610277575f600319360112610277576020604051620151808152f35b61143661138536610ccc565b6113a661139760208395949501612c27565b6113a081611c0f565b15612c31565b6113bc6001610d31855f525f60205260405f2090565b9060c08161141b6114146108ec6107146113e060208901516001600160a01b031690565b6114078b8a60408101938960806113fe87516001600160a01b031690565b9301519361494b565b516001600160a01b031690565b54876149c5565b6040519586928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f567044ba1cdd4671ac3979c114241e1e3b56c9e9051f63f2f234f7a2795019cc9361044693610bcc925f92611499575b50611492368561301c565b9087614b52565b6114b391925060c03d60c011610ad357610ac58183612707565b905f611487565b34610277576114c836610816565b906114da6006610b2e60208501612c27565b6114e761089b3683612c79565b91602082016114f581612ced565b9061150960408501926080610b6c85612ced565b611515610b858461316b565b9361151f86614c9c565b1561155657505050610bd881610bcc7f587faad1bcd589ce902468251883e1976a645af8563c773eed7356d78433210c9386614cf9565b6115a59060a08561157261156c87989697612ced565b89615053565b60405194859283927eea54e700000000000000000000000000000000000000000000000000000000845260048401613773565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af48015610488577f17eb0a6bd5a0de45d1029ce3444941070e149df35b22176fc439f930f73c09f795610bd895610bcc945f93611614575b50610c8261160291612ced565b9161160d368761301c565b8a8a6150bb565b611602919350611635610c829160a03d60a011611355576113468183612707565b9391506115f5565b6024359060ff8216820361027757565b359060ff8216820361027757565b34610277576040600319360112610277576001600160a01b036116a96004356116838161048d565b8261168c61163d565b91165f52600760205260405f209060ff165f5260205260405f2090565b541660405180916001600160a01b0360208301911682520390f35b60806003193601126102775760043560243567ffffffffffffffff8111610277576116f3903690600401610807565b60443567ffffffffffffffff811161027757611713903690600401610249565b919061171d61027b565b9061172f855f525f60205260405f2090565b9161173c6001840161323d565b9161176661174b855460ff1690565b611754816121c1565b600181149081156119c2575b506139e5565b86611773600586016128a0565b916117b46117808861316b565b67ffffffffffffffff6117ab61179e875167ffffffffffffffff1690565b67ffffffffffffffff1690565b91161015613a14565b60208501516001600160a01b0316976117d760408701516001600160a01b031690565b9367ffffffffffffffff6117ff61179e6117f08c61316b565b935167ffffffffffffffff1690565b9116116118c3575b94611867889795857f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a9b6118616118859760149d61185561187c996118959c6118b49f60808c015192613eff565b9391949092369061301c565b9061405d565b845460ff191660021785555163ffffffff1690565b63ffffffff1690565b67ffffffffffffffff4216613a43565b9301805467ffffffffffffffff191667ffffffffffffffff8516179055565b61044660405192839283613a65565b909296959397946118df61190a9389888a60808601519361494b565b60c08761141b6119036108ec8c6001600160a01b03165f52600660205260405f2090565b548d6149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4938415610488577f07b9206d5a6026d3bd2a8f9a9b79f6fa4bfbd6a016975829fbaf07488019f28a99896118b4986118618e8c61185560149f976118679861187c9b61198a6118959f6118859f5f916119a3575b508d611983368961301c565b9089615443565b9a9f5050995050509750509b5095509597985050611807565b6119bc915060c03d60c011610ad357610ac58183612707565b5f611977565b600491506119cf816121c1565b145f611760565b34610277576080600319360112610277576004356119f38161048d565b6119fb61163d565b90604435611a088161048d565b60643567ffffffffffffffff811161027757611b4a6001600160a01b0392611b22611a6396611b07611b02611a4289973690600401610249565b60ff85169a91611afc90611a578d1515613a8d565b8b89169d8e1515612b9e565b611abf8785611ab9611aad611aad611aa085611a90866001600160a01b03165f52600760205260405f2090565b9060ff165f5260205260405f2090565b546001600160a01b031690565b6001600160a01b031690565b15613abc565b6040805160ff891660208201526001600160a01b038b169181019190915246606080830191909152815292611af5608085612707565b3691612fcb565b9061564c565b613b01565b611a90856001600160a01b03165f52600760205260405f2090565b906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b167f2366b94a706a0cfc2dca2fe8be9410b6fba2db75e3e9d3f03b3c2fb0b051efad5f80a4005b611b91611b7d36610ccc565b6113a66003610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f188e0ade7d115cc397426774adb960ae3e8c83e72f0a6cad4b7085e1d60bf9869361044693610bcc925f926114995750611492368561301c565b634e487b7160e01b5f52602160045260245ffd5b60041115611c0a57565b611bec565b600a1115611c0a57565b90600a821015611c0a5752565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b61084d9167ffffffffffffffff8251168152611c6f60208301516020830190611c19565b60408201516040820152611cdd6060830151606083019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b608082810151805167ffffffffffffffff1661014084015260208101516001600160a01b0316610160840152604081015160ff1661018084015260608101516101a0840152908101516101c083015260a08101516101e083015260c0015161020082015260c0611d5f60a0840151610260610220850152610260840190611c26565b92015190610240818403910152611c26565b929367ffffffffffffffff60c09561084d98979482948752611d9281611c00565b602087015216604085015216606083015260808201528160a08201520190611c4b565b3461027757602060031936011261027757600435611dd1613b66565b505f52600260205260405f20611de561272a565b9080548252610644600182015491611e31611e21611e038560ff1690565b94611e12602088019687613baa565b60081c6001600160a01b031690565b6001600160a01b03166040860152565b611e58611e4860028301546001600160a01b031690565b6001600160a01b03166060860152565b60038101546080850152600481015467ffffffffffffffff811660a086019081529490611e90905b60401c67ffffffffffffffff1690565b67ffffffffffffffff1660c0820190815291611ee46117f0611ec0600660058501549460e08701958652016128a0565b93610100810194855251965197611ed689611c00565b5167ffffffffffffffff1690565b905191519260405196879687611d71565b3461027757611f0336610816565b611f146008610b2e60208401612c27565b80611f89611f2561089b3686612c79565b936020810160c0611f3582612ced565b91611f546040850193611f4785612ced565b608087013591898c61494b565b610de2610ddb610dc4610714611f6a368b61301c565b9687958d8a611f7882614c9c565b9d8e15612052575b50505050612ced565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af491821561048857611fc6935f9361202d575b50611fc0903690612c79565b86614b52565b15611ffc576104467f3142fb397e715d80415dff7b527bf1c451def4675da6e1199ee1b4588e3f630a91604051918291826130ca565b6104467f26afbcb9eb52c21f42eb9cfe8f263718ffb65afbf84abe8ad8cce2acfb2242b891604051918291826130ca565b611fc091935061204b9060c03d60c011610ad357610ac58183612707565b9290611fb4565b6120aa936120876109a6926120696109bd9561483e565b8c606061207a366101408501612f3c565b9101526060369101612f3c565b60808c01526120946132b5565b60a08c01526120a16132b5565b8c8c0152612ced565b505f8d8a8e611f80565b9160a09367ffffffffffffffff9161084d97969385526120d381611c00565b602085015216604083015260608201528160808201520190611c4b565b346102775760206003193601126102775760043561210c613b66565b505f52600560205260405f2061212061273c565b908054825261064460018201549161213e611e21611e038560ff1690565b612155611e4860028301546001600160a01b031690565b60038101546080850152600481015467ffffffffffffffff1667ffffffffffffffff1660a08501908152936121b061219b600660058501549460c08501958652016128a0565b9160e0810192835251945195611ed687611c00565b9151905191604051958695866120b4565b60061115611c0a57565b906006821015611c0a5752565b9192612250610120946121f285612263959a99989a6121cb565b602085019060a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b61014060e0840152610140830190611c4b565b946101008201520152565b34610277576020600319360112610277576004355f60a0604051612291816126ae565b82815282602082015282604082015282606082015282608082015201526122b6613b66565b505f525f6020526122c960405f20613bc2565b80516122d4816121c1565b61064460208301519260408101519060606122fd61179e608084015167ffffffffffffffff1690565b91015191604051958695866121d8565b346102775761231b3661049e565b90916123316001600160a01b0382161515612b9e565b61233c821515612bcd565b335f5260066020526123628360405f20906001600160a01b03165f5260205260405f2090565b54908282106123f75782820391821161059a578383916123b7936123b18361239b336001600160a01b03165f52600660205260405f2090565b906001600160a01b03165f5260205260405f2090565b55614d9d565b7fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb6001600160a01b0360405193169280610bd83394829190602083019252565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b61243f61242b36610ccc565b6113a66002610b2e60208496959601612c27565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4928315610488577f6085f5128b19e0d3cc37524413de47259383f0f75265d5d66f417786962066969361044693610bcc925f926114995750611492368561301c565b34610277576124a836610f1f565b6124b0615810565b6001600160a01b038116916124c6831515612b9e565b6001600160a01b036124ed8261239b336001600160a01b03165f52600860205260405f2090565b54916124fa831515612bcd565b5f61251a8261239b336001600160a01b03165f52600860205260405f2090565b55169181836125945761253d915f808080858a5af1612537613c20565b50613c4f565b60405190815233907f7b8d70738154be94a9a068a6d2f5dd8cfc65c52855859dc8f47de1ff185f8b5590602090a4610fda60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b50506040517fa9059cbb000000000000000000000000000000000000000000000000000000005f52836004528160245260205f60448180875af160015f511481161561261a575b60409190915261253d577f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b03821660045260245ffd5b6001811516612630573d15843b151516166125db565b503d5f823e3d90fd5b3461027757610fda61264a36610fdc565b91613c90565b34610277575f60031936011261027757602060405160018152f35b1561267257565b7fc60f1e78000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b60c0810190811067ffffffffffffffff8211176126ca57604052565b61269a565b60e0810190811067ffffffffffffffff8211176126ca57604052565b60a0810190811067ffffffffffffffff8211176126ca57604052565b90601f601f19910116810190811067ffffffffffffffff8211176126ca57604052565b6040519061273a61012083612707565b565b6040519061273a61010083612707565b6040519061273a60e083612707565b90604051612768816126cf565b60c0600482946127a660ff825467ffffffffffffffff811687526001600160a01b03808260401c1616602088015260e01c16604086019060ff169052565b6001810154606085015260028101546080850152600381015460a08501520154910152565b90600182811c921680156127f9575b60208310146127e557565b634e487b7160e01b5f52602260045260245ffd5b91607f16916127da565b5f9291815491612812836127cb565b8083529260018116908115612867575060011461282e57505050565b5f9081526020812093945091925b83831061284d575060209250010190565b60018160209294939454838587010152019101919061283c565b9050602094955060ff1991509291921683830152151560051b010190565b9061273a6128999260405193848092612803565b0383612707565b906040516128ad816126cf565b809260ff815467ffffffffffffffff8116845260401c1690600a821015611c0a57600d61291f9160c0936020860152600181015460408601526128f26002820161275b565b60608601526129036007820161275b565b6080860152612914600c8201612885565b60a086015201612885565b910152565b5190600482101561027757565b67ffffffffffffffff81160361027757565b5190811515820361027757565b908160c0910312610277576129b860a06040519261296d846126ae565b805184526020810151602085015261298760408201612924565b6040850152606081015161299a81612931565b606085015260808101516129ad81612931565b608085015201612943565b60a082015290565b9081516129cc81611c00565b815260a0806129ea602085015160c0602086015260c0850190611c4b565b936040810151604085015267ffffffffffffffff606082015116606085015267ffffffffffffffff6080820151166080850152015191015290565b90602061084d9281815201906129c0565b6040513d5f823e3d90fd5b92916020612b8d61273a9360408752612a75815467ffffffffffffffff811660408a015260ff60608a019160401c16611c19565b60018101546080880152600281015467ffffffffffffffff811660a0890152604081901c6001600160a01b031660c089015260e090811c60ff16908801526003810154610100880152600481015461012088015260058101546101408801526006810154610160880152600781015467ffffffffffffffff8116610180890152604081901c6001600160a01b03166101a089015260e01c60ff166101c088015260088101546101e08801526009810154610200880152600a810154610220880152600b81015461024088015261026080880152600d612b5b6102a08901600c8401612803565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0898403016102808a015201612803565b94019067ffffffffffffffff169052565b15612ba557565b7fe6c4247b000000000000000000000000000000000000000000000000000000005f5260045ffd5b15612bd457565b7f69640e72000000000000000000000000000000000000000000000000000000005f5260045ffd5b634e487b7160e01b5f52601160045260245ffd5b9190820180921161059a57565b600a111561027757565b3561084d81612c1d565b15612c3857565b7fc898513c000000000000000000000000000000000000000000000000000000005f5260045ffd5b63ffffffff81160361027757565b359061273a82612931565b91908260c091031261027757604051612c91816126ae565b60a08082948035612ca181612c60565b84526020810135612cb18161048d565b60208501526040810135612cc48161048d565b60408501526060810135612cd781612931565b6060850152608081013560808501520135910152565b3561084d8161048d565b908160c09103126102775760405190612d0f826126ae565b805182526020810151602083015260408101516006811015610277576129b89160a09160408501526060810151612d4581612931565b60608501526129ad60808201612943565b90612d628183516121cb565b608067ffffffffffffffff81612d87602086015160a0602087015260a0860190611c4b565b94604081015160408601526060810151606086015201511691015290565b359061273a82612c1d565b60c0809167ffffffffffffffff8135612dc881612931565b1684526001600160a01b036020820135612de18161048d565b16602085015260ff612df56040830161164d565b166040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b9035601e198236030181121561027757016020813591019167ffffffffffffffff821161027757813603831361027757565b601f8260209493601f1993818652868601375f8582860101520116010190565b61084d9167ffffffffffffffff8235612e8a81612931565b168152612ea86020830135612e9e81612c1d565b6020830190611c19565b60408201356040820152612ec26060820160608401612db0565b612ed461014082016101408401612db0565b612f08612efc612ee8610220850185612e20565b610260610220860152610260850191612e52565b92610240810190612e20565b91610240818503910152612e52565b9091612f2e61084d93604084526040840190612d56565b916020818403910152612e72565b91908260e091031261027757604051612f54816126cf565b60c08082948035612f6481612931565b84526020810135612f748161048d565b6020850152612f856040820161164d565b6040850152606081013560608501526080810135608085015260a081013560a08501520135910152565b67ffffffffffffffff81116126ca57601f01601f191660200190565b929192612fd782612faf565b91612fe56040519384612707565b829481845281830111610277578281602093845f960137010152565b9080601f830112156102775781602061084d93359101612fcb565b919091610260818403126102775761303261274c565b9261303c82612c6e565b845261304a60208301612da5565b6020850152604082013560408501526130668160608401612f3c565b6060850152613079816101408401612f3c565b608085015261022082013567ffffffffffffffff8111610277578161309f918401613001565b60a085015261024082013567ffffffffffffffff8111610277576130c39201613001565b60c0830152565b90602061084d928181520190612e72565b60e09060a061084d949363ffffffff81356130f581612c60565b1683526001600160a01b03602082013561310e8161048d565b1660208401526001600160a01b03604082013561312a8161048d565b16604084015267ffffffffffffffff606082013561314781612931565b16606084015260808101356080840152013560a08201528160c08201520190612e72565b3561084d81612931565b9091612f2e61084d936040845260408401906129c0565b634e487b7160e01b5f52603260045260245ffd5b6003548110156131b85760035f5260205f2001905f90565b61318c565b80548210156131b8575f5260205f2001905f90565b916131eb9183549060031b91821b915f19901b19161790565b9055565b600354680100000000000000008110156126ca57600181016003556003548110156131b85760035f527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0155565b9060405161324a816126ae565b60a0600382946001600160a01b03815463ffffffff8116865260201c1660208501526132a467ffffffffffffffff60018301546001600160a01b03808216166040880152851c16606086019067ffffffffffffffff169052565b600281015460808501520154910152565b604051906132c4602083612707565b5f8252565b90916132e061084d93604084526040840190612d56565b916020818403910152611c4b565b67ffffffffffffffff81116126ca5760051b60200190565b60405190613315602083612707565b5f808352366020840137565b9061332b826132ee565b6133386040519182612707565b828152601f1961334882946132ee565b0190602036910137565b9190820391821161059a57565b80518210156131b85760209160051b010190565b9190600354908084029380850482149015171561059a57818410156133f75783019081841161059a578082116133ef575b506133b76133b28483613352565b613321565b92805b8281106133c657505050565b806133d56106986001936131a0565b6133e86133e28584613352565b8861335f565b52016133ba565b90505f6133a4565b5050905061084d613306565b906006811015611c0a5760ff60ff198354169116179055565b90602061084d928181520190611c4b565b9061343f825f525f60205260405f2090565b61344b6001820161323d565b91613457825460ff1690565b9184613465600583016128a0565b91604086019261347c84516001600160a01b031690565b91600261349360208a01516001600160a01b031690565b9761349d816121c1565b1480613654575b6135995750506134e16108f86108ec6107146134fc9661140760c0978a978c898f6080906134d96001610b2e60208601612c27565b01519361494b565b6040519384928392632a2d120f60e21b845260048401612f17565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af4801561048857610f10613573946109a688937f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a898613566965f92613578575b5061355f368961301c565b9086614b52565b50604051918291826130ca565b0390a2565b61359291925060c03d60c011610ad357610ac58183612707565b905f613554565b7f04cd8c68bf83e7bc531ca5a5d75c34e36513c2acf81e07e6470ba79e29da13a89750613573969195506136479450610f10926135fe6014836135e66109a695600360ff19825416179055565b5f60138201550167ffffffffffffffff198154169055565b606087016136278151606061361d60208301516001600160a01b031690565b9101519086614d9d565b5160a061363e60208301516001600160a01b031690565b91015191614d9d565b506040519182918261341c565b5061366d61179e601483015467ffffffffffffffff1690565b42116134a4565b1561367b57565b7fdb1ea1ac000000000000000000000000000000000000000000000000000000005f5260045ffd5b906136ad81611c00565b60ff60ff198354169116179055565b908160a0910312610277576137116080604051926136d9846126eb565b80518452602081015160208501526136f360408201612924565b6040850152606081015161370681612931565b606085015201612943565b608082015290565b90815161372581611c00565b815260806001600160a01b038161374b602086015160a0602087015260a0860190611c4b565b946040810151604086015267ffffffffffffffff606082015116606086015201511691015290565b9091612f2e61084d93604084526040840190613719565b9161379483614c9c565b613959576137aa825f52600560205260405f2090565b6137b684825414613674565b60018101918254916137d2836001600160a01b039060081c1690565b9360026137f26137eb828501546001600160a01b031690565b9560ff1690565b6137fb81611c00565b1480613939575b6138bf5750600361383b9161381e6007610b2e60208701612c27565b019261382e84548287868b61494b565b60a0836115728389615053565b038173893f2d45fdffe2d4297a5c1d5732edce4849ee825af4908115610488577f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d19561389995610bcc945f9461389e575b50549261160d368761301c565b0390a3565b6138b891945060a03d60a011611355576113468183612707565b925f61388c565b7f2fdac1380dbe23ae259b6871582b7f33e34461547f400bdd20d74991250317d1945090613899936138fb610bcc93600360ff19825416179055565b613933600d60058401935f855495556139226004820167ffffffffffffffff198154169055565b015460401c6001600160a01b031690565b90614d9d565b5061395261179e600484015467ffffffffffffffff1690565b4211613802565b6138997f6d0cf3d243d63f08f50db493a8af34b27d4e3bc9ec4098e82700abfeffe2d49891610bcc613992865f525f60205260405f2090565b6139be60026139af60018401546001600160a01b039060201c1690565b9201546001600160a01b031690565b908388615025565b5f19811461059a5760010190565b90602061084d928181520190613719565b156139ec57565b7ff2056b18000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613a1b57565b7f7d957361000000000000000000000000000000000000000000000000000000005f5260045ffd5b9067ffffffffffffffff8091169116019067ffffffffffffffff821161059a57565b9067ffffffffffffffff613a86602092959495604085526040850190612e72565b9416910152565b15613a9457565b7f06ee4dcd000000000000000000000000000000000000000000000000000000005f5260045ffd5b15613ac5575050565b906001600160a01b0360ff927f0bcc40f3000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15613b0857565b7fc1606c2f000000000000000000000000000000000000000000000000000000005f5260045ffd5b60405190613b3d826126cf565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b60405190613b73826126cf565b606060c0835f81525f60208201525f6040820152613b8f613b30565b83820152613b9b613b30565b60808201528260a08201520152565b613bb382611c00565b52565b6006821015611c0a5752565b90604051613bcf816126eb565b608067ffffffffffffffff60148395613bec60ff82541686613bb6565b613bf86001820161323d565b6020860152613c09600582016128a0565b604086015260138101546060860152015416910152565b3d15613c4a573d90613c3182612faf565b91613c3f6040519384612707565b82523d5f602084013e565b606090565b15613c58575050565b6001600160a01b03907fa5b05eec000000000000000000000000000000000000000000000000000000005f521660045260245260445ffd5b91613c9a83614c9c565b613e8157613cb0825f52600260205260405f2090565b613cbc84825414613674565b6001810191825491613cd8836001600160a01b039060081c1690565b936002613cf16137eb828501546001600160a01b031690565b613cfa81611c00565b1480613e5e575b613de457506003613d6591613d1d6005610b2e60208701612c27565b0192613d2d84548287868b61494b565b60c083610bf1613d5e613d51856001600160a01b03165f52600660205260405f2090565b61073d6101608501612ced565b5489614206565b038173728904e52308213ba61c90ef49f34c18fbda9e115af4908115610488577f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e9561389995610bcc945f94613dc3575b505492610c93368761301c565b613ddd91945060c03d60c011610481576104708183612707565b925f613db6565b7f1b92e8ef67d8a7c0d29c99efcd180a5e0d98d60ac41d52abbbb5950882c78e4e94509061389993613e20610bcc93600360ff19825416179055565b613933600d60058401935f85549555613922600482017fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff8154169055565b506004820154613e7a9060401c67ffffffffffffffff1661179e565b4211613d01565b6138997f32e24720f56fd5a7f4cb219d7ff3278ae95196e79c85b5801395894a6f53466c91610bcc613992865f525f60205260405f2090565b15613ec3575050565b906001600160a01b0360ff927f577f5940000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b93929190918215613fd157843560f81c9081613f4c57507f000000000000000000000000000000000000000000000000000000000000000094600101925f19019150613f489050565b9091565b600180915f97939594975060ff86161c1603613fa957613f9d83613f8b611aa0613f4896611a908a6001600160a01b03165f52600760205260405f2090565b966001600160a01b0388161515613eba565b600101915f1990910190565b7f1a9073b4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fac241e11000000000000000000000000000000000000000000000000000000005f5260045ffd5b805191908290602001825e015f815290565b60021115611c0a57565b90816020910312610277575190565b939260609361404f6001600160a01b0394613a86949998998852608060208901526080880190611c26565b918683036040880152612e52565b906001600160a01b03929560209761409195996140c861407f61410d95615887565b6140ba604051998a928e840190613ff9565b7f6368616c6c656e67650000000000000000000000000000000000000000000000815260090190565b03601f198101895288612707565b6140d18161400b565b61415a57505b604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b0392165afa80156104885761273a915f9161412b575b501515613b01565b61414d915060203d602011614153575b6141458183612707565b810190614015565b5f614123565b503d61413b565b90506140d7565b5f6040519161416f836126ae565b81835261420260208401614181613b66565b81526141f46040860191858352611e806004606089019288845260808a01958987526141bc60a08c01998b8b525f52600260205260405f2090565b9160ff6001840154166141ce81611c00565b8c526141dc600684016128a0565b90526005820154905201549167ffffffffffffffff83165b67ffffffffffffffff169052565b5290565b9060405191614214836126ae565b5f835261420260208401614226613b66565b81526141f460408601915f8352611e80600460608901925f845260808a01955f87526141bc60a08c01995f8b525f52600260205260405f2090565b7f8000000000000000000000000000000000000000000000000000000000000000811461059a575f0390565b6020939291614329919796976142ab815f52600260205260405f2090565b976040860180516142bb81611c00565b6142c481611c00565b61450a575b5089888660a08901956142dc8751151590565b6144f6575b5050505050506142fc606085015167ffffffffffffffff1690565b67ffffffffffffffff81166144cb575b50608084015167ffffffffffffffff168061447c575b5051151590565b1561446357608001518201516001600160a01b031680935b8251905f82131561442357614363915061435b8451615ad0565b928391614527565b61437260058601918254612c10565b90555b0180515f8113156143d15750916143b060059261239b6143986143c69651615ad0565b966001600160a01b03165f52600660205260405f2090565b6143bb858254613352565b905501918254612c10565b90555b61273a61467e565b9290505f83126143e5575b505050506143c9565b61440260059261239b6143986143fd61441897614261565b615ad0565b61440d858254612c10565b905501918254613352565b90555f8080806143dc565b5f8212614433575b505050614375565b6144426143fd61444a93614261565b928391614d9d565b61445960058601918254613352565b9055825f8061442b565b50600d84015460401c6001600160a01b03168093614341565b6144c59060048901907fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff6fffffffffffffffff000000000000000083549260401b169116179055565b5f614322565b6144f090600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f61430c565b6144ff956159a2565b5f80898886836142e1565b614521905161451881611c00565b60018b016136a3565b5f6142c9565b9061453a9291614535615810565b61458f565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b1561456757565b7fd2ade556000000000000000000000000000000000000000000000000000000005f5260045ffd5b908215614679576001600160a01b0316918215801561466a576145b3823414614560565b156145bd57505050565b6001600160a01b03604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52166004523060245260445260205f60648180865af160015f5114811615614654575b6040919091525f606052156146205750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f526001600160a01b031660045260245ffd5b6001811516612630573d15833b1515161661460e565b6146743415614560565b6145b3565b505050565b6003546004545f5b82821080614776575b1561476b576146a36106a2610698846131a0565b6001810160036146b4825460ff1690565b6146bd81611c00565b14614759576146cb82615b4d565b1561471657915f8261076961470d95600561470796019261075a61075285549261073d600d61072b61071460028501546001600160a01b031690565b916139c6565b915b9190614686565b5050915061472390600455565b8061472b5750565b6040519081527f61815f4b11c6ea4e14a2e448a010bed8efdc3e53a15efbf183d16a31085cd14590602090a1565b505090614765906139c6565b9161470f565b915061472390600455565b506040811061468f565b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f01000000000000000000000000000000000000000000000000000000000000009160405161482760208201809360a0809163ffffffff81511684526001600160a01b0360208201511660208501526001600160a01b03604082015116604085015267ffffffffffffffff6060820151166060850152608081015160808501520151910152565b60c0815261483660e082612707565b519020161790565b602081013561484c8161048d565b6001600160a01b03811690614862821515612b9e565b6040830135906148718261048d565b6148986001600160a01b0383169261488a841515612b9e565b6148938361048d565b61048d565b6148a18161048d565b5081146148ed575063ffffffff6201518091356148bd81612c60565b16106148c557565b7f0596b15b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fabfa558d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b903590601e1981360301821215610277570180359067ffffffffffffffff82116102775760200191813603831361027757565b929161273a9461497c61498b92614971838761496b610220890189614918565b90613eff565b90878a949394615b81565b8361496b610240850185614918565b92909194615b81565b604051906149a1826126eb565b5f6080838281526149b0613b66565b60208201528260408201528260608201520152565b90601467ffffffffffffffff916149da614994565b935f525f60205260405f20906149f460ff83541686613bb6565b614a00600583016128a0565b6020860152601382015460408601526060850152015416608082015290565b9060a060039163ffffffff8151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784556001600160a01b036020820151167fffffffffffffffff0000000000000000000000000000000000000000ffffffff77ffffffffffffffffffffffffffffffffffffffff0000000086549260201b169116178455614b4160018501614aef614ac660408501516001600160a01b031690565b82906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b606083015181547fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff1660a09190911b7bffffffffffffffff000000000000000000000000000000000000000016179055565b608081015160028501550151910155565b92614b8e81614bde9460a094614b6f885f525f60205260405f2090565b97614b7b895460ff1690565b614b84816121c1565b15614c5a57615443565b604081018051614b9d816121c1565b614ba6816121c1565b151580614c2f575b614c15575b50601484018054606083015167ffffffffffffffff9081169116819003614bed575b50500151151590565b614be55750565b60135f910155565b614c0e919067ffffffffffffffff1667ffffffffffffffff19825416179055565b5f80614bd5565b614c299051614c23816121c1565b85613403565b5f614bb3565b50845460ff16815190614c41826121c1565b614c4a826121c1565b614c53816121c1565b1415614bae565b614c678260018b01614a1f565b615443565b9067ffffffffffffffff604051916020830193845216604082015260408152614c96606082612707565b51902090565b805f525f60205260ff60405f2054166006811015611c0a578015908115614ce5575b50614ce0575f525f60205267ffffffffffffffff600760405f20015416461490565b505f90565b60059150614cf2816121c1565b145f614cbe565b90614d3b91614d146001610d31835f525f60205260405f2090565b60c0836108ff614d346108ec61071460408701516001600160a01b031690565b54856149c5565b03817378d150fda6fa6739c18014b347c7c7c45c58e1485af49283156104885761273a945f94614d78575b50614d7290369061301c565b91614b52565b614d72919450614d969060c03d60c011610ad357610ac58183612707565b9390614d66565b9061453a9291614dab615810565b9190918115614679576001600160a01b0383169283614e4f576001600160a01b038216925f8080808488620186a0f1614de2613c20565b5015614def575050505050565b614e326138999261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614e3d828254612c10565b90556040519081529081906020820190565b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152602081602481885afa908115610488575f91615006575b506040517fa9059cbb00000000000000000000000000000000000000000000000000000000602082019081526001600160a01b0385166024830152604480830187905282525f91829190614ee7606482612707565b51908286620186a0f190614ef9613c20565b506040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526020816024818a5afa9182156104885786915f93614fe5575b5083614fda575b83614fc6575b50505015614f5b575b50505050565b81614fa46001600160a01b039261239b7fbf182be802245e8ed88e4b8d3e4344c0863dd2a70334f089fd07265389306fcf956001600160a01b03165f52600860205260405f2090565b614faf858254612c10565b90556040519384521691602090a35f808080614f55565b614fd1929350613352565b145f8481614f4c565b818110159350614f46565b614fff91935060203d602011614153576141458183612707565b915f614f3f565b61501f915060203d602011614153576141458183612707565b5f614e92565b909192614d14614d3b946150456001610d31865f525f60205260405f2090565b92608084015191868661494b565b906001600160a01b0390615065614994565b925f52600560205267ffffffffffffffff600460405f2060ff60018201541661508d81611c00565b865261509b600682016128a0565b602087015260058101546040870152015416606084015216608082015290565b6020939291614329919796976150d9815f52600560205260405f2090565b976040860180516150e981611c00565b6150f281611c00565b615179575b50898886608089019561510a8751151590565b615165575b50505050505061512a606085015167ffffffffffffffff1690565b67ffffffffffffffff8116615140575051151590565b6144c590600489019067ffffffffffffffff1667ffffffffffffffff19825416179055565b61516e95615d2e565b5f808988868361510f565b615187905161451881611c00565b5f6150f7565b90600a811015611c0a577fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff68ff000000000000000083549260401b169116179055565b9060c060049161520367ffffffffffffffff825116859067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015184546040808401517fffffff000000000000000000000000000000000000000000ffffffffffffffff90921692901b7bffffffffffffffffffffffffffffffffffffffff0000000000000000169190911760e09190911b7cff0000000000000000000000000000000000000000000000000000000016178455606081015160018501556080810151600285015560a081015160038501550151910155565b601f82116152b357505050565b5f5260205f20906020601f840160051c830193106152eb575b601f0160051c01905b8181106152e0575050565b5f81556001016152d5565b90915081906152cc565b919091825167ffffffffffffffff81116126ca5761531d8161531784546127cb565b846152a6565b6020601f82116001146153585781906131eb9394955f9261534d575b50508160011b915f199060031b1c19161790565b015190505f80615339565b601f1982169061536b845f5260205f2090565b915f5b8181106153a55750958360019596971061538d575b505050811b019055565b01515f1960f88460031b161c191690555f8080615383565b9192602060018192868b01518155019401920161536e565b8151815467ffffffffffffffff191667ffffffffffffffff91909116178155602082015191600a831015611c0a5760c0600d916153fd61273a958561518d565b604081015160018501556154186060820151600286016151d0565b6154296080820151600786016151d0565b61543a60a0820151600c86016152f5565b015191016152f5565b6154596060919493945f525f60205260405f2090565b936154676080850151151590565b61563a575b01916154bf60a06154886020865101516001600160a01b031690565b9280515f81136155fc575b506020810180515f81136155b4575b5081515f8112615573575b50515f8112615528575b500151151590565b8061551a575b6154d6575b5050505061273a61467e565b61550f9261550260a0926154f6604060139601516001600160a01b031690565b90848451015191614d9d565b5101519201918254613352565b90555f8080806154ca565b5060a08351015115156154c5565b6143fd61553491614261565b61554f8561239b61071460408a01516001600160a01b031690565b61555a828254612c10565b905561556b60138901918254613352565b90555f6154b7565b6143fd61557f91614261565b61559d818761559860208b01516001600160a01b031690565b614d9d565b6155ac60138a01918254613352565b90555f6154ad565b6155bd90615ad0565b6155d88661239b61071460408b01516001600160a01b031690565b6155e3828254613352565b90556155f460138a01918254612c10565b90555f6154a2565b61560590615ad0565b615623818661561e60208a01516001600160a01b031690565b614527565b61563260138901918254612c10565b90555f615493565b61564781600587016153bd565b61546c565b805192835f947a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008210156157e8575b806d04ee2d6d415b85acef8100000000600a9210156157cc575b662386f26fc100008110156157b7575b6305f5e1008110156157a5575b612710811015615795575b6064811015615786575b101561577b575b61571260216156da60018801615ddf565b968701015b5f1901917f3031323334353637383961626364656600000000000000000000000000000000600a82061a8353600a900490565b90811561572257615712906156df565b50506001600160a01b036157478461573b858498615d73565b60208151910120615dc9565b9116931683146157735761576591816020611aad9351910120615dc9565b1461576e575f90565b600190565b505050600190565b6001909401936156c9565b600290606490049601956156c2565b60049061271090049601956156b8565b6008906305f5e10090049601956156ad565b601090662386f26fc1000090049601956156a0565b6020906d04ee2d6d415b85acef81000000009004960195615690565b50604094507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615676565b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00541461585f5760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b67ffffffffffffffff815116906020810151600a811015611c0a576159308260406159919401516158cf60806060840151930151946040519760208901526040880190611c19565b6060860152608085019060c0809167ffffffffffffffff81511684526001600160a01b03602082015116602085015260ff6040820151166040850152606081015160608501526080810151608085015260a081015160a08501520151910152565b805167ffffffffffffffff1661016084015260208101516001600160a01b0316610180840152604081015160ff166101a084015260608101516101c084015260808101516101e084015260a081015161020084015260c00151610220830152565b610220815261084d61024082612707565b93909291935f52600260205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b602081015192600a841015611c0a57615a5660c0615aa093615a0e615acc9760039a61518d565b60408101516007890155615a29606082015160088a016151d0565b615a3a6080820151600d8a016151d0565b615a4b60a082015160128a016152f5565b0151601387016152f5565b60018501907fffffffffffffffffffffff0000000000000000000000000000000000000000ff74ffffffffffffffffffffffffffffffffffffffff0083549260081b169116179055565b60028301906001600160a01b031673ffffffffffffffffffffffffffffffffffffffff19825416179055565b0155565b5f8112615ada5790565b7fa8ce4432000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90604051918281549182825260208201905f5260205f20925f5b818110615b3457505061273a92500383612707565b8454835260019485019487945060209093019201615b1f565b67ffffffffffffffff6004820154164210159081615b69575090565b600180925060ff91015416615b7d81611c00565b1490565b6001600160a01b039061410d615ba7615ba26020989599969799369061301c565b615887565b93604051988997889687957f600109bb00000000000000000000000000000000000000000000000000000000875260048701614024565b6001810190825f528160205260405f2054155f14615c46578054680100000000000000008110156126ca57615c33615c1d8260018794018555846131bd565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b5050505f90565b80548015615c74575f190190615c6382826131bd565b8154905f199060031b1b1916905555565b634e487b7160e01b5f52603160045260245ffd5b6001810191805f528260205260405f2054928315155f14615d26575f19840184811161059a5783545f1981019490851161059a575f958583615ce397615cd69503615ce9575b505050615c4d565b905f5260205260405f2090565b55600190565b615d0f615d0991615d00610698615d1d95886131bd565b928391876131bd565b906131d2565b85905f5260205260405f2090565b555f8080615cce565b505050505f90565b93909291935f52600560205260405f2092835560068301936159e767ffffffffffffffff825116869067ffffffffffffffff1667ffffffffffffffff19825416179055565b61273a90615dbb615db594936040519586937f19457468657265756d205369676e6564204d6573736167653a0a0000000000006020860152603a850190613ff9565b90613ff9565b03601f198101845283612707565b61084d91615dd691615e06565b90929192615e40565b90615de982612faf565b615df66040519182612707565b828152601f196133488294612faf565b8151919060418303615e3657615e2f9250602082015190606060408401519301515f1a90615f07565b9192909190565b50505f9160029190565b615e4981611c00565b80615e52575050565b615e5b81611c00565b60018103615e8b577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b615e9481611c00565b60028103615ec857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b80615ed4600392611c00565b14615edc5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615f7e579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610488575f516001600160a01b03811615615f7457905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a1d82448e7e3f611b69660be3f5dc6e070ca23bb9e11aefc6fc6b7622d1cdc4e64736f6c634300081e00330000000000000000000000002a35728cadd8076dfd424fc3e20974a3cd03bfa5", + "nonce": "0x6", + "chainId": "0x13882" + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "status": "0x1", + "cumulativeGasUsed": "0x1410b0", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000008e12cca7c1e150000000000000000000000000000000000000000000000000a467417de9a33bd1000000000000000000000000000000000000000000002b297badcd163bced3e7000000000000000000000000000000000000000000000000a3d92eb141e15a81000000000000000000000000000000000000000000002b297c3bdfe2e390b537", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "transactionIndex": "0x0", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x1410b0", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x214c9e", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000005db48e1b848b52000000000000000000000000000000000000000000000000a3d92eb13cf13f31000000000000000000000000000000000000000000002b297c3bdfe2e390b537000000000000000000000000000000000000000000000000a37b7a23216cb3df000000000000000000000000000000000000000000002b297c999470ff154089", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionIndex": "0x1", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "transactionIndex": "0x1", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0xd3bee", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x2e18fc", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000005a9ea056b694e2000000000000000000000000000000000000000000000000a37b7a231e2af44d000000000000000000000000000000000000000000002b297c999470ff154089000000000000000000000000000000000000000000000000a320db82c7745f6b000000000000000000000000000000000000000000002b297cf4331155cbd56b", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionIndex": "0x2", + "logIndex": "0x2", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "transactionIndex": "0x2", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0xccc5e", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "contractAddress": null + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x34a379", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x000000000000000000000000000000000000000000000000002e505f361a7163000000000000000000000000000000000000000000000000a320db82c44e1449000000000000000000000000000000000000000000002b297cf4331155cbd56b000000000000000000000000000000000000000000000000a2f28b238e33a2e6000000000000000000000000000000000000000000002b297d2283708be646ce", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionIndex": "0x3", + "logIndex": "0x3", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "transactionIndex": "0x3", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x68a7d", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x2a35728cadd8076dfd424fc3e20974a3cd03bfa5" + }, + { + "status": "0x1", + "cumulativeGasUsed": "0x867909", + "logs": [ + { + "address": "0x0000000000000000000000000000000000001010", + "topics": [ + "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", + "0x0000000000000000000000000000000000000000000000000000000000001010", + "0x000000000000000000000000f8bedb0aba14833e95f29e760487c3d34bc4ec64", + "0x0000000000000000000000007ee41d8a25641000661b1ef5e6ae8a00400466b0" + ], + "data": "0x00000000000000000000000000000000000000000000000002436f597d48d070000000000000000000000000000000000000000000000000a2f28b238c978e23000000000000000000000000000000000000000000002b297d2283708be646ce000000000000000000000000000000000000000000000000a0af1bca0f4ebdb3000000000000000000000000000000000000000000002b297f65f2ca092f173e", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "blockTimestamp": "0x69ac3613", + "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionIndex": "0x4", + "logIndex": "0x4", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000008000000000000000000800000000000000000000100000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000020000000400000000000000000000000200000000000000200000000000000000000040000000000000000000000000000000000000004000000000000000000001000000000000000000400000000000100000000000000000000000000000000000000000000000000000000000000000000000100000", + "type": "0x2", + "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "transactionIndex": "0x4", + "blockHash": "0x5b8deca35a551794826ebcca7605fce43a993be6996101ac202c51bdc1dc1a56", + "blockNumber": "0x2144623", + "gasUsed": "0x51d590", + "effectiveGasPrice": "0x714a1d19e", + "from": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "to": null, + "contractAddress": "0x55d6f0a0322606447fbc612cf58014faed65af9d" + } + ], + "libraries": [ + "src/ChannelEngine.sol:ChannelEngine:0x78D150fdA6fa6739C18014B347c7c7C45C58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine:0x728904E52308213bA61C90EF49F34c18Fbda9E11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine:0x893F2D45fDFFe2D4297a5C1D5732EDce4849eE82" + ], + "pending": [], + "returns": {}, + "timestamp": 1772893715802, + "chain": 80002, + "commit": "4b4244f1" +} \ No newline at end of file diff --git a/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-03-07T14-12-00.json b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-03-07T14-12-00.json new file mode 100644 index 000000000..57fb626de --- /dev/null +++ b/contracts/deployments/11155111/ChannelEngine.sol_ChannelEngine/2026-03-07T14-12-00.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "transactionHash": "0x3df2187dc8a50ef62abfeb377318888493042770315492070c4708584dfbf572", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772892720, + "chainId": 11155111, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-03-07T14-12-00.json b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-03-07T14-12-00.json new file mode 100644 index 000000000..98c8bed71 --- /dev/null +++ b/contracts/deployments/11155111/ChannelHub.sol_ChannelHub/2026-03-07T14-12-00.json @@ -0,0 +1,18 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0xb7bE0E2007dDF320d680942cb9e008F986E74F83", + "transactionHash": "0x6e0b716f9bdb40d3aadbfa2544bf5ec11b39f431736bd19569ade187cb0b7396", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772892720, + "chainId": 11155111, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x735EB1026aFbA78B602dA39C6B59EABa95753686" + ], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82" + }, + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-12-00.json b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-12-00.json new file mode 100644 index 000000000..a06da0154 --- /dev/null +++ b/contracts/deployments/11155111/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-12-00.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x735EB1026aFbA78B602dA39C6B59EABa95753686", + "transactionHash": "0x5e58e1f709d9ded21112c24523733b843486bf6ae775ffd10d86118a5c947cfe", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772892720, + "chainId": 11155111, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-12-00.json b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-12-00.json new file mode 100644 index 000000000..63dff18e4 --- /dev/null +++ b/contracts/deployments/11155111/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-12-00.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "transactionHash": "0x6e81a9f20bb7b3370a15b6402271a9f8e7eae63184e733c80273c497a4187983", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772892720, + "chainId": 11155111, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-12-00.json b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-12-00.json new file mode 100644 index 000000000..caccab516 --- /dev/null +++ b/contracts/deployments/11155111/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-12-00.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "transactionHash": "0x9712dcbc9f46d075bb90ab9d5cbbdf30195810bb050150b302cb3aaaf0e71bc0", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772892720, + "chainId": 11155111, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-05T12-51-13.json b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-05T12-51-13.json new file mode 100644 index 000000000..f7c065d34 --- /dev/null +++ b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-05T12-51-13.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0xB1aA0ac73B5E648a57db2d9342f11c471FcC85F1", + "transactionHash": "0xd0fe4560dec28164525e91b32484e0a2804b1727a4660c0e7581613e55c67b84", + "commit": "7e9af291fd864bc6d6d2b7b1295710781ac7c288", + "timestamp": 1772715073, + "chainId": 11155111, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow", + "YELLOW", + "18", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "10000000000000000000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-37-37.json b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-37-37.json new file mode 100644 index 000000000..67dd29de9 --- /dev/null +++ b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-37-37.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0xD3E8Eb01Ae895262f187c4aAe936eC5c0665bbf8", + "transactionHash": "0xd58f896bd20194a337cf6c7e64e2da1344b9b05bf9bdf7e3891fb197f424cd06", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772890657, + "chainId": 11155111, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "YUSD", + "6", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "10000000000000000000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-52-25.json b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-52-25.json new file mode 100644 index 000000000..c91d63b54 --- /dev/null +++ b/contracts/deployments/11155111/PremintERC20.sol_PremintERC20/2026-03-07T13-52-25.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x719a00F9e8b831335F156337cEF7dC48986b2e84", + "transactionHash": "0x1ec139129b2bb3192f1827834b5fddeab6ea32f35d52b92074d4025d200ce90f", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772891545, + "chainId": 11155111, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "BNB", + "BNB", + "18", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "1000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-18-38.json b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-18-38.json new file mode 100644 index 000000000..1dc2448a9 --- /dev/null +++ b/contracts/deployments/11155111/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-18-38.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x2aC63456d78Cf2E2FDAf45cbed45b5d29907f4ac", + "transactionHash": "0xc60132f8c0d6776ab20861ff4c3137a3e23e4b5719cedda4bde866f435024488", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893118, + "chainId": 11155111, + "contractPath": "./src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/2026-03-07T14-28-35.json b/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/2026-03-07T14-28-35.json new file mode 100644 index 000000000..07566ce76 --- /dev/null +++ b/contracts/deployments/80002/ChannelEngine.sol_ChannelEngine/2026-03-07T14-28-35.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "transactionHash": "0x0dd22ca5c6e28580d5cd04bc74d1df7d6612757840f54d0bcae73a4424db0213", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893715, + "chainId": 80002, + "contractPath": "src/ChannelEngine.sol:ChannelEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/ChannelHub.sol_ChannelHub/2026-03-07T14-28-35.json b/contracts/deployments/80002/ChannelHub.sol_ChannelHub/2026-03-07T14-28-35.json new file mode 100644 index 000000000..ea85dbb4d --- /dev/null +++ b/contracts/deployments/80002/ChannelHub.sol_ChannelHub/2026-03-07T14-28-35.json @@ -0,0 +1,18 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x55D6f0A0322606447fbc612Cf58014Faed65aF9D", + "transactionHash": "0x566204df2ef25ad1f33d2b4abde5de21dd12b8865a8166780b4a6e588073355e", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893715, + "chainId": 80002, + "contractPath": "src/ChannelHub.sol:ChannelHub", + "constructorArgs": [ + "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5" + ], + "libraries": { + "src/ChannelEngine.sol:ChannelEngine": "0x78d150fda6fa6739c18014b347c7c7c45c58e148", + "src/EscrowDepositEngine.sol:EscrowDepositEngine": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82" + }, + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-28-35.json b/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-28-35.json new file mode 100644 index 000000000..919323887 --- /dev/null +++ b/contracts/deployments/80002/ECDSAValidator.sol_ECDSAValidator/2026-03-07T14-28-35.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x2A35728CADd8076dfD424fC3e20974A3CD03bFa5", + "transactionHash": "0x5f3ea2e02ec5b886970dd186c7f06ed18e105477627f3759174a677323e2c735", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893715, + "chainId": 80002, + "contractPath": "src/sigValidators/ECDSAValidator.sol:ECDSAValidator", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-28-35.json b/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-28-35.json new file mode 100644 index 000000000..264024e04 --- /dev/null +++ b/contracts/deployments/80002/EscrowDepositEngine.sol_EscrowDepositEngine/2026-03-07T14-28-35.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x728904e52308213ba61c90ef49f34c18fbda9e11", + "transactionHash": "0x51c15e27975ec5e84bc358c5df10f0c247451091d86e75cb8a81e80f1739bfe6", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893715, + "chainId": 80002, + "contractPath": "src/EscrowDepositEngine.sol:EscrowDepositEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-28-35.json b/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-28-35.json new file mode 100644 index 000000000..d73d580b5 --- /dev/null +++ b/contracts/deployments/80002/EscrowWithdrawalEngine.sol_EscrowWithdrawalEngine/2026-03-07T14-28-35.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xf8bedb0aba14833e95f29e760487c3d34bc4ec64", + "deployedTo": "0x893f2d45fdffe2d4297a5c1d5732edce4849ee82", + "transactionHash": "0x2e9b6ee81c54ea9035cb524b77ad6fa6bfe4340f4abacb482a5b280b94031d74", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772893715, + "chainId": 80002, + "contractPath": "src/EscrowWithdrawalEngine.sol:EscrowWithdrawalEngine", + "constructorArgs": [], + "comment": "clearnet-sandbox-1" +} diff --git a/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-49-56.json b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-49-56.json new file mode 100644 index 000000000..da5e9657c --- /dev/null +++ b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-49-56.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x0827b6aAA03475A8BF59Ee1A2bD76903DDFbaDB6", + "transactionHash": "0x3be3284aab87ed63c1a37041aa6d804b4141cbcb754133376be09adc2f729c95", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772891396, + "chainId": 80002, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "Yellow USD", + "YUSD", + "8", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "1000000000000000000000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-53-02.json b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-53-02.json new file mode 100644 index 000000000..7827d7f9b --- /dev/null +++ b/contracts/deployments/80002/PremintERC20.sol_PremintERC20/2026-03-07T13-53-02.json @@ -0,0 +1,17 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x9d8193e5655a36FFB9CD7D88D31c91d2650896D0", + "transactionHash": "0x459e4a275cc3a4acea78eed1017f30cc5c2c5ee240b0ba1365ca8f5c963c9180", + "commit": "fd39408567c4793fa5872f853bf875a45f12a4e9", + "timestamp": 1772891582, + "chainId": 80002, + "contractPath": "./src/PremintERC20.sol:PremintERC20", + "constructorArgs": [ + "BNB", + "BNB", + "18", + "0xd29995d8511Fe2dc1031F2650f950Adf4ECceBAD", + "1000000000000" + ], + "comment": "" +} diff --git a/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-29-19.json b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-29-19.json new file mode 100644 index 000000000..074f477ae --- /dev/null +++ b/contracts/deployments/80002/SessionKeyValidator.sol_SessionKeyValidator/2026-03-07T14-29-19.json @@ -0,0 +1,11 @@ +{ + "deployer": "0xF8bedb0aBa14833e95F29e760487C3d34Bc4Ec64", + "deployedTo": "0x87825ACa5f4B9c3dc8B5aa3352724eDF5135D892", + "transactionHash": "0xb093a4bda220b56c44fb0cd62ec98938433830cb8262db0ec37d7b3571514cb3", + "commit": "4b4244f1df8ac6aa87a848174e29eabc7c1c3ede", + "timestamp": 1772893759, + "chainId": 80002, + "contractPath": "./src/sigValidators/SessionKeyValidator.sol:SessionKeyValidator", + "constructorArgs": [], + "comment": "" +} diff --git a/contracts/foundry.lock b/contracts/foundry.lock new file mode 100644 index 000000000..5424e6004 --- /dev/null +++ b/contracts/foundry.lock @@ -0,0 +1,11 @@ +{ + "lib/forge-std": { + "tag": { + "name": "v1.15.0", + "rev": "0844d7e1fc5e60d77b68e469bff60265f236c398" + } + }, + "lib/openzeppelin-contracts": { + "rev": "fcbae5394ae8ad52d8e580a3477db99814b9d565" + } +} \ No newline at end of file diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 700f9701c..455fe6f90 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -11,10 +11,10 @@ optimizer_runs = 1_000_000 # special compiler profile for ChannelHub to prevent code size overflow additional_compiler_profiles = [ - { name = "channelhub", optimizer_runs = 4_000 } + { name = "channelhub", optimizer_runs = 750 } ] # compile ChannelHub with lower optimizer runs to stay within size limits compilation_restrictions = [ - { paths = "src/ChannelHub.sol", optimizer_runs = 4_000 } + { paths = "src/ChannelHub.sol", optimizer_runs = 750 } ] diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std index 1801b0541..0844d7e1f 160000 --- a/contracts/lib/forge-std +++ b/contracts/lib/forge-std @@ -1 +1 @@ -Subproject commit 1801b0541f4fda118a10798fd3486bb7051c5dd6 +Subproject commit 0844d7e1fc5e60d77b68e469bff60265f236c398 diff --git a/contracts/script/DeployChannelHub.s.sol b/contracts/script/DeployChannelHub.s.sol new file mode 100644 index 000000000..1d12e14f8 --- /dev/null +++ b/contracts/script/DeployChannelHub.s.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; + +import {ChannelHub} from "../src/ChannelHub.sol"; +import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; +import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; + +/** + * @title DeployChannelHub + * @notice Forge script to deploy engine libraries and ChannelHub + * @dev Foundry automatically deploys unlinked libraries (ChannelEngine, + * EscrowDepositEngine, EscrowWithdrawalEngine) before ChannelHub in the + * broadcast batch. Their addresses appear in the broadcast JSON output. + * + * Usage: + * DEFAULT_VALIDATOR_ADDR= Address of an already-deployed ISignatureValidator. + * Leave unset to deploy a fresh ECDSAValidator. + * + * forge script script/DeployChannelHub.s.sol:DeployChannelHub \ + * --rpc-url \ + * --private-key \ + * --broadcast \ + * [-vvvv] + * + */ +contract DeployChannelHub is Script { + function run() external { + // Optional: reuse an existing validator or deploy a fresh ECDSAValidator + address defaultValidatorAddr = vm.envOr("DEFAULT_VALIDATOR_ADDR", address(0)); + run(defaultValidatorAddr); + } + + function run(address defaultValidatorAddr) public { + // msg.sender is set by Foundry to the address derived from --private-key + address deployer = msg.sender; + + console.log("=== Deploy ChannelHub ==="); + console.log("Deployer: ", deployer); + console.log("Chain ID: ", block.chainid); + + // ---------------------------------------------------------------- + // Predict addresses for informational logging. + // NOTE: Unlinked libraries (ChannelEngine, EscrowDepositEngine, + // EscrowWithdrawalEngine) are deployed by Foundry via the CREATE2 + // deployer (0x4e59b44847b379578588920ca78fbf26c0b4956c) and do NOT + // consume the deployer's CREATE nonce. Their exact addresses appear + // in the broadcast JSON after the run. + // ---------------------------------------------------------------- + uint64 nonce = vm.getNonce(deployer); + + bool deployValidator = defaultValidatorAddr == address(0); + if (deployValidator) { + console.log("ECDSAValidator: ", vm.computeCreateAddress(deployer, nonce)); + nonce++; + } else { + console.log("DefaultValidator: ", defaultValidatorAddr); + } + + // Libraries are deployed via the CREATE2 deployer; they do not affect + // the deployer's nonce sequence, so ChannelHub lands at nonce `nonce`. + console.log( + "ChannelEngine/EscrowDepositEng/EscrowWithdrawEng: deployed via CREATE2 deployer (see broadcast JSON)" + ); + console.log("ChannelHub: ", vm.computeCreateAddress(deployer, nonce)); + + vm.startBroadcast(); + + // 1. Deploy default signature validator if not provided + if (deployValidator) { + ECDSAValidator ecdsaValidator = new ECDSAValidator(); + defaultValidatorAddr = address(ecdsaValidator); + console.log("Deployed ECDSAValidator:", defaultValidatorAddr); + } + + // 2. Deploy ChannelHub. + // Foundry detects unlinked library references (ChannelEngine, + // EscrowDepositEngine, EscrowWithdrawalEngine) and inserts their + // deployment transactions before this one in the broadcast batch. + require( + defaultValidatorAddr.code.length > 0, + "DeployChannelHub: DEFAULT_VALIDATOR_ADDR has no code - must be a deployed contract" + ); + ChannelHub hub = new ChannelHub(ISignatureValidator(defaultValidatorAddr)); + + vm.stopBroadcast(); + + // ---------------------------------------------------------------- + // Summary + // ---------------------------------------------------------------- + console.log(""); + console.log("=== Deployment complete ==="); + console.log("DefaultSigValidator:", defaultValidatorAddr); + console.log("ChannelHub: ", address(hub)); + console.log("(Library addresses are logged in the broadcast JSON)"); + } +} diff --git a/contracts/src/ChannelEngine.sol b/contracts/src/ChannelEngine.sol index a2ee66179..2bb804a13 100644 --- a/contracts/src/ChannelEngine.sol +++ b/contracts/src/ChannelEngine.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.30; +pragma solidity ^0.8.30; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {ChannelStatus, State, StateIntent} from "./interfaces/Types.sol"; @@ -26,6 +26,7 @@ library ChannelEngine { error IncorrectPreviousStateIntent(); error IncorrectChannelStatus(); error IncorrectStateVersion(); + error ChallengeExpired(); error IncorrectUserAllocation(); error IncorrectNodeAllocation(); @@ -53,18 +54,18 @@ library ChannelEngine { uint256 lockedFunds; uint256 nodeAvailableFunds; uint64 challengeExpiry; + bool isParametricToken; + uint48 channelSubId; } struct TransitionEffects { // Fund movements (positive = pull/lock, negative = push/release) int256 userFundsDelta; // Funds to pull from user (>0) or push to user (<0) int256 nodeFundsDelta; // Funds to lock from node vault (>0) or release (<0) - // State updates ChannelStatus newStatus; uint64 newChallengeExpiry; bool updateLastState; - bool clearDispute; bool closeChannel; } @@ -100,6 +101,12 @@ library ChannelEngine { // homeLedger always represents current chain require(candidate.homeLedger.chainId == block.chainid, IncorrectHomeChainId()); require(candidate.version > ctx.prevState.version || Utils.isEmpty(ctx.prevState), IncorrectStateVersion()); + if (ctx.isParametricToken) { + require( + Utils.isEmpty(ctx.prevState) || candidate.homeLedger.token == ctx.prevState.homeLedger.token, + "Parametric token cannot change during channel lifetime" + ); + } // Validate token decimals for homeLedger Utils.validateTokenDecimals(candidate.homeLedger); @@ -124,6 +131,11 @@ library ChannelEngine { require(netFlowsSum >= 0, NegativeNetFlowSum()); require(allocsSum == netFlowsSum.toUint256(), IncorrectAllocationSum()); + + // If channel is DISPUTED, check that challenge hasn't expired + if (ctx.status == ChannelStatus.DISPUTED) { + require(block.timestamp <= ctx.challengeExpiry, ChallengeExpired()); + } } // ========== Internal: Phase 2 - Intent-Specific Calculation ========== @@ -183,7 +195,7 @@ library ChannelEngine { effects.userFundsDelta = userNfDelta; // Pull deposit from user effects.nodeFundsDelta = nodeNfDelta; // May lock more from node or release effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -205,7 +217,7 @@ library ChannelEngine { effects.userFundsDelta = userNfDelta; // Negative = push to user effects.nodeFundsDelta = nodeNfDelta; effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -228,7 +240,7 @@ library ChannelEngine { // Calculate effects effects.nodeFundsDelta = nodeNfDelta; // Only node balance adjustments effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -259,6 +271,7 @@ library ChannelEngine { effects.userFundsDelta = userNfDelta; effects.nodeFundsDelta = nodeNfDelta; effects.newStatus = ChannelStatus.CLOSED; + effects.newChallengeExpiry = 0; effects.closeChannel = true; return effects; @@ -294,7 +307,7 @@ library ChannelEngine { // Calculate effects effects.nodeFundsDelta = nodeNfDelta; // Only node balance adjustments effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -313,10 +326,6 @@ library ChannelEngine { || ctx.status == ChannelStatus.MIGRATING_IN, IncorrectChannelStatus() ); - require(candidate.homeLedger.nodeAllocation == 0, IncorrectNodeAllocation()); - // nothing changes from initiate escrow deposit state - require(userNfDelta == 0, IncorrectUserNetFlowDelta()); - require(nodeNfDelta == 0, IncorrectNodeNetFlowDelta()); // Check home - non-home state consistency require(ctx.prevState.intent == StateIntent.INITIATE_ESCROW_DEPOSIT, IncorrectPreviousStateIntent()); @@ -324,6 +333,11 @@ library ChannelEngine { require(candidate.nonHomeLedger.userAllocation == 0, IncorrectUserAllocation()); require(candidate.nonHomeLedger.nodeAllocation == 0, IncorrectNodeAllocation()); + require(candidate.homeLedger.nodeAllocation == 0, IncorrectNodeAllocation()); + // nothing changes from initiate escrow deposit state + require(userNfDelta == 0, IncorrectUserNetFlowDelta()); + require(nodeNfDelta == 0, IncorrectNodeNetFlowDelta()); + uint256 depositAmount = ctx.prevState.nonHomeLedger.userAllocation; require(candidate.nonHomeLedger.userNetFlow == depositAmount.toInt256(), IncorrectUserNetFlow()); require(candidate.nonHomeLedger.nodeNetFlow == -depositAmount.toInt256(), IncorrectNodeNetFlow()); @@ -339,7 +353,7 @@ library ChannelEngine { effects.userFundsDelta = 0; effects.nodeFundsDelta = 0; effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -372,7 +386,7 @@ library ChannelEngine { // Calculate effects - no immediate fund movement effects.nodeFundsDelta = nodeNfDelta; // Only node balance adjustments effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -404,7 +418,7 @@ library ChannelEngine { // Calculate effects effects.nodeFundsDelta = nodeNfDelta; // Only node balance adjustments effects.newStatus = ChannelStatus.OPERATING; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; return effects; } @@ -469,7 +483,8 @@ library ChannelEngine { // Calculate effects - may adjust node vault based on net flow delta effects.nodeFundsDelta = nodeNfDelta; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newStatus = ChannelStatus.OPERATING; + effects.newChallengeExpiry = 0; } else { revert IncorrectChannelStatus(); } @@ -523,7 +538,7 @@ library ChannelEngine { // Calculate effects - release all currently locked funds to node vault effects.nodeFundsDelta = nodeNfDelta; effects.newStatus = ChannelStatus.MIGRATED_OUT; - effects.clearDispute = (ctx.status == ChannelStatus.DISPUTED); + effects.newChallengeExpiry = 0; effects.closeChannel = true; } else { revert IncorrectChannelStatus(); diff --git a/contracts/src/ChannelHub.sol b/contracts/src/ChannelHub.sol index 9621cff22..3deac68a9 100644 --- a/contracts/src/ChannelHub.sol +++ b/contracts/src/ChannelHub.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.30; +pragma solidity ^0.8.30; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -9,6 +10,7 @@ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +import {IParametricToken} from "./interfaces/IParametricToken.sol"; import {IVault} from "./interfaces/IVault.sol"; import {ISignatureValidator, ValidationResult, VALIDATION_FAILURE} from "./interfaces/ISignatureValidator.sol"; import { @@ -32,7 +34,7 @@ import {EcdsaSignatureUtils} from "./sigValidators/EcdsaSignatureUtils.sol"; * @notice Main contract implementing the Nitrolite state channel protocol (single-chain operations) * @dev Uses unified transition pattern with ChannelEngine library for validation */ -contract ChannelHub is IVault, ReentrancyGuard { +contract ChannelHub is IVault, ReentrancyGuard, Ownable { using EnumerableSet for EnumerableSet.Bytes32Set; using SafeERC20 for IERC20; using SafeCast for int256; @@ -73,8 +75,17 @@ contract ChannelHub is IVault, ReentrancyGuard { event MigrationInFinalized(bytes32 indexed channelId, State state); event ValidatorRegistered(address indexed node, uint8 indexed validatorId, ISignatureValidator indexed validator); - event TransferFailed(address indexed recipient, address indexed token, uint256 amount); - event FundsClaimed(address indexed account, address indexed token, address indexed destination, uint256 amount); + event TransferFailed(uint48 indexed fromSubId, address indexed recipient, address indexed token, uint256 amount); + event FundsClaimed( + address indexed account, + address indexed token, + uint48 subId, + address indexed destination, + uint256 amount + ); + event NodeBalanceUpdated(address indexed node, address indexed token, uint48 indexed subId, uint256 amount); + + event ParametricTokenIsSet(address indexed token, bool isParametric); error InvalidAddress(); error IncorrectAmount(); @@ -94,7 +105,8 @@ contract ChannelHub is IVault, ReentrancyGuard { error IncorrectStateIntent(); error IncorrectChannelStatus(); error ChallengerVersionTooLow(); - error OnlyNonHomeEscrowsCanBeChallenged(); + error NoChannelIdFoundForEscrow(); + error IncorrectChannelId(); struct ChannelMeta { ChannelStatus status; @@ -102,6 +114,7 @@ contract ChannelHub is IVault, ReentrancyGuard { State lastState; uint256 lockedFunds; uint64 challengeExpireAt; + uint48 subId; } struct EscrowDepositMeta { @@ -158,29 +171,34 @@ contract ChannelHub is IVault, ReentrancyGuard { mapping(bytes32 escrowId => EscrowWithdrawalMeta meta) internal _escrowWithdrawals; + mapping(address token => bool) public isParametricToken; + mapping(address node => mapping(address token => uint256 balance)) internal _nodeBalances; + mapping(address node => mapping(address token => mapping(uint48 subId => uint256 balance))) + internal _nodeSubBalances; // Validator ID 0x00 is reserved for DEFAULT_SIG_VALIDATOR // Validator IDs 0x01-0xFF are available for node-registered validators - mapping(address node => mapping(uint8 validatorId => ISignatureValidator validator)) internal - _nodeValidatorRegistry; + mapping(address node => mapping(uint8 validatorId => ISignatureValidator validator)) + internal _nodeValidatorRegistry; // Reclaim balances for failed outbound transfers // Accumulates funds when transfers fail (blacklists, hooks, gas depletion) // Users can claim these funds later via claimFunds() mapping(address account => mapping(address token => uint256 amount)) internal _reclaims; + mapping(address account => mapping(address token => mapping(uint48 subId => uint256 amount))) internal _subReclaims; // ========== Constructor ========== - constructor(ISignatureValidator _defaultSigValidator) { + constructor(ISignatureValidator _defaultSigValidator) Ownable(msg.sender) { require(address(_defaultSigValidator) != address(0), InvalidAddress()); DEFAULT_SIG_VALIDATOR = _defaultSigValidator; } // ========== Getters ========== - function getAccountBalance(address node, address token) external view returns (uint256) { - return _nodeBalances[node][token]; + function getAccountBalance(address node, address token, uint48 subId) external view returns (uint256) { + return !isParametricToken[token] ? _nodeBalances[node][token] : _nodeSubBalances[node][token][subId]; } function getNodeValidator(address node, uint8 validatorId) external view returns (ISignatureValidator) { @@ -191,6 +209,10 @@ contract ChannelHub is IVault, ReentrancyGuard { return _userChannels[user].values(); } + function getChannelSubId(bytes32 channelId) external view returns (uint48) { + return _channels[channelId].subId; + } + // Filter only non-closed and non-migrated-out channels function getOpenChannels(address user) external view returns (bytes32[] memory openChannels) { openChannels = _userChannels[user].values(); @@ -211,7 +233,9 @@ contract ChannelHub is IVault, ReentrancyGuard { } } - function getChannelData(bytes32 channelId) + function getChannelData( + bytes32 channelId + ) external view returns ( @@ -230,7 +254,9 @@ contract ChannelHub is IVault, ReentrancyGuard { lockedFunds = meta.lockedFunds; } - function getEscrowDepositData(bytes32 escrowId) + function getEscrowDepositData( + bytes32 escrowId + ) external view returns ( @@ -251,7 +277,9 @@ contract ChannelHub is IVault, ReentrancyGuard { initState = meta.initState; } - function getEscrowWithdrawalData(bytes32 escrowId) + function getEscrowWithdrawalData( + bytes32 escrowId + ) external view returns ( @@ -274,31 +302,57 @@ contract ChannelHub is IVault, ReentrancyGuard { return _reclaims[account][token]; } + function getSubReclaimBalance(address account, address token, uint48 subId) external view returns (uint256) { + return _subReclaims[account][token][subId]; + } + + // ========= Setters ========= + + function setParametricToken(address token, bool isParametric) external onlyOwner { + isParametricToken[token] = isParametric; + // Optional: emit event + emit ParametricTokenIsSet(token, isParametric); + } + // ========= IVault ========== - function depositToVault(address node, address token, uint256 amount) external payable { + function depositToVault(address node, address token, uint48 subId, uint256 amount) external payable { require(node != address(0), InvalidAddress()); require(amount > 0, IncorrectAmount()); - _nodeBalances[node][token] += amount; + uint256 nodeBalance = _getNodeBalance(node, token, subId); + uint256 updatedBalance = nodeBalance + amount; + if (!isParametricToken[token]) { + _nodeBalances[node][token] = updatedBalance; + } else { + _nodeSubBalances[node][token][subId] = updatedBalance; + } - _pullFunds(msg.sender, token, amount); + _pullFunds(msg.sender, subId, token, amount); - emit Deposited(node, token, amount); + emit Deposited(node, token, subId, amount); + emit NodeBalanceUpdated(node, token, subId, updatedBalance); } - function withdrawFromVault(address to, address token, uint256 amount) external { + function withdrawFromVault(address to, address token, uint48 subId, uint256 amount) external { require(to != address(0), InvalidAddress()); require(amount > 0, IncorrectAmount()); - uint256 currentBalance = _nodeBalances[msg.sender][token]; - require(currentBalance >= amount, InsufficientBalance()); + address node = msg.sender; - _nodeBalances[msg.sender][token] = currentBalance - amount; + uint256 nodeBalance = _getNodeBalance(node, token, subId); + require(nodeBalance >= amount, InsufficientBalance()); + uint256 updatedBalance = nodeBalance - amount; + if (!isParametricToken[token]) { + _nodeBalances[node][token] = updatedBalance; + } else { + _nodeSubBalances[node][token][subId] = updatedBalance; + } - _pushFunds(to, token, amount); + _pushFunds(subId, to, token, amount); - emit Withdrawn(msg.sender, token, amount); + emit Withdrawn(node, token, subId, amount); + emit NodeBalanceUpdated(node, token, subId, updatedBalance); } /** @@ -307,24 +361,39 @@ contract ChannelHub is IVault, ReentrancyGuard { * @param token The token address (address(0) for native ETH) * @param destination The destination address to send funds to (can differ from msg.sender for blacklisted users) */ - function claimFunds(address token, address destination) external nonReentrant { + function claimFunds(address token, uint48 subId, address destination) external nonReentrant { require(destination != address(0), InvalidAddress()); address account = msg.sender; - uint256 amount = _reclaims[account][token]; + uint256 amount = 0; + + if (!isParametricToken[token]) { + amount = _reclaims[account][token]; + } else { + amount = _subReclaims[account][token][subId]; + } + require(amount > 0, IncorrectAmount()); - _reclaims[account][token] = 0; + if (!isParametricToken[token]) { + _reclaims[account][token] = 0; + } else { + _subReclaims[account][token][subId] = 0; + } // Transfer without gas limit or reclaim logic (user controls gas, accepts responsibility) if (token == address(0)) { - (bool success,) = payable(destination).call{value: amount}(""); + (bool success, ) = payable(destination).call{value: amount}(""); require(success, NativeTransferFailed(destination, amount)); } else { - IERC20(token).safeTransfer(destination, amount); + if (!isParametricToken[token]) { + IERC20(token).safeTransfer(destination, amount); + } else { + IParametricToken(token).transferFromSub(subId, destination, amount); + } } - emit FundsClaimed(account, token, destination, amount); + emit FundsClaimed(account, token, subId, destination, amount); } // ========= Escrow Deposit Purge ========== @@ -386,11 +455,16 @@ contract ChannelHub is IVault, ReentrancyGuard { } // Only INITIALIZED escrows can be purged; CHALLENGED escrows require manual finalization if (_isEscrowDepositUnlockable(meta)) { - _nodeBalances[meta.node][meta.initState.nonHomeLedger.token] += meta.lockedAmount; + uint256 updatedBalance = + _nodeBalances[meta.node][meta.initState.nonHomeLedger.token] + meta.lockedAmount; + _nodeBalances[meta.node][meta.initState.nonHomeLedger.token] = updatedBalance; + meta.status = EscrowStatus.FINALIZED; meta.lockedAmount = 0; purgedCount++; escrowHeadTemp++; + + emit NodeBalanceUpdated(meta.node, meta.initState.nonHomeLedger.token, 0, updatedBalance); } else { break; } @@ -449,18 +523,37 @@ contract ChannelHub is IVault, ReentrancyGuard { // to create a channel and perform initial operation simultaneously function createChannel(ChannelDefinition calldata def, State calldata initState) external payable { require( - initState.intent == StateIntent.DEPOSIT || initState.intent == StateIntent.WITHDRAW - || initState.intent == StateIntent.OPERATE, + initState.intent == StateIntent.DEPOSIT || + initState.intent == StateIntent.WITHDRAW || + initState.intent == StateIntent.OPERATE, IncorrectStateIntent() ); bytes32 channelId = Utils.getChannelId(def, VERSION); + address token = initState.homeLedger.token; + + // Determine subId based on token type + uint48 subId = 0; + if (isParametricToken[token]) { + IParametricToken parametricToken = IParametricToken(token); + + if (parametricToken.accountType(address(this)) != IParametricToken.AccountType.Super) { + parametricToken.convertToSuper(address(this)); + } + + subId = parametricToken.createSubAccount(address(this)); + } + + _channels[channelId].subId = subId; + _requireValidDefinition(def); _validateSignatures(channelId, initState, def.user, def.node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][initState.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext( + channelId, + _nodeBalances[def.node][initState.homeLedger.token] + ); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, initState); _applyEffects(channelId, def, initState, effects); @@ -478,15 +571,25 @@ contract ChannelHub is IVault, ReentrancyGuard { emit ChannelCreated(channelId, def.user, def.node, def, initState); } + function _getNodeBalance(address node, address token, uint48 subId) internal view returns (uint256) { + if (isParametricToken[token]) { + return _nodeSubBalances[node][token][subId]; + } else { + return _nodeBalances[node][token]; + } + } + function depositToChannel(bytes32 channelId, State calldata candidate) public payable { require(candidate.intent == StateIntent.DEPOSIT, IncorrectStateIntent()); ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory def = meta.definition; + + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext(channelId, nodeBalance); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -499,10 +602,12 @@ contract ChannelHub is IVault, ReentrancyGuard { ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory def = meta.definition; + + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext(channelId, nodeBalance); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -515,10 +620,12 @@ contract ChannelHub is IVault, ReentrancyGuard { ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory def = meta.definition; + + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + _validateSignatures(channelId, candidate, def.user, def.node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext(channelId, nodeBalance); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -546,19 +653,22 @@ contract ChannelHub is IVault, ReentrancyGuard { // If version is higher, process the new state if (candidate.version > prevState.version) { - require(candidate.intent == StateIntent.OPERATE, IncorrectStateIntent()); _validateSignatures(channelId, candidate, user, node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[node][candidate.homeLedger.token]); + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + + ChannelEngine.TransitionContext memory ctx = _buildChannelContext(channelId, nodeBalance); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyTransitionEffects(channelId, def, candidate, effects); } // else: challenging with same version, state already processed - (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, node, def.approvedSignatureValidators); + (ISignatureValidator validator, bytes calldata sigData) = _extractValidator( + challengerSig, + node, + def.approvedSignatureValidators + ); _validateChallengerSignature(channelId, candidate, sigData, validator, user, node, challengerIdx); meta.status = ChannelStatus.DISPUTED; @@ -569,8 +679,6 @@ contract ChannelHub is IVault, ReentrancyGuard { } function closeChannel(bytes32 channelId, State calldata candidate) external payable { - require(candidate.intent == StateIntent.CLOSE, IncorrectStateIntent()); - ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory def = meta.definition; ChannelStatus status = meta.status; @@ -580,13 +688,13 @@ contract ChannelHub is IVault, ReentrancyGuard { address user = def.user; // Path 1: Unilateral closure after challenge timeout - if (status == ChannelStatus.DISPUTED && block.timestamp > meta.challengeExpireAt) { + if (status == ChannelStatus.DISPUTED && meta.challengeExpireAt < block.timestamp) { meta.status = ChannelStatus.CLOSED; meta.lockedFunds = 0; meta.challengeExpireAt = 0; - _pushFunds(user, prevState.homeLedger.token, prevState.homeLedger.userAllocation); - _pushFunds(node, prevState.homeLedger.token, prevState.homeLedger.nodeAllocation); + _pushFunds(meta.subId, user, prevState.homeLedger.token, prevState.homeLedger.userAllocation); + _pushFunds(meta.subId, node, prevState.homeLedger.token, prevState.homeLedger.nodeAllocation); _userChannels[user].remove(channelId); @@ -595,10 +703,12 @@ contract ChannelHub is IVault, ReentrancyGuard { } // Path 2: Cooperative closure with signed CLOSE state + require(candidate.intent == StateIntent.CLOSE, IncorrectStateIntent()); _validateSignatures(channelId, candidate, user, node, def.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][candidate.homeLedger.token]); + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + + ChannelEngine.TransitionContext memory ctx = _buildChannelContext(channelId, nodeBalance); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, def, candidate, effects); @@ -623,11 +733,19 @@ contract ChannelHub is IVault, ReentrancyGuard { } else { // NON-HOME CHAIN: Create escrow record - recover addresses from signatures EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId, 0); - EscrowDepositEngine.TransitionEffects memory effects = - EscrowDepositEngine.validateTransition(ctx, candidate); + EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateTransition( + ctx, + candidate + ); _applyEscrowDepositEffects( - escrowId, channelId, candidate, effects, def.user, def.node, def.approvedSignatureValidators + escrowId, + channelId, + candidate, + effects, + def.user, + def.node, + def.approvedSignatureValidators ); _escrowDepositIds.push(escrowId); @@ -635,69 +753,103 @@ contract ChannelHub is IVault, ReentrancyGuard { } } - function challengeEscrowDeposit(bytes32 escrowId, bytes calldata challengerSig, ParticipantIndex challengerIdx) - external - { + function challengeEscrowDeposit( + bytes32 escrowId, + bytes calldata challengerSig, + ParticipantIndex challengerIdx + ) external { EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; - require(!_isHomeChain(meta.channelId), OnlyNonHomeEscrowsCanBeChallenged()); - bytes32 channelId = meta.channelId; - (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, meta.node, meta.approvedSignatureValidators); - _validateChallengerSignature(channelId, meta.initState, sigData, validator, meta.user, meta.node, challengerIdx); + require(channelId != bytes32(0), NoChannelIdFoundForEscrow()); + + (ISignatureValidator validator, bytes calldata sigData) = _extractValidator( + challengerSig, + meta.node, + meta.approvedSignatureValidators + ); + _validateChallengerSignature( + channelId, + meta.initState, + sigData, + validator, + meta.user, + meta.node, + challengerIdx + ); EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext(escrowId, 0); EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateChallenge(ctx); _applyEscrowDepositEffects( - escrowId, channelId, meta.initState, effects, meta.user, meta.node, meta.approvedSignatureValidators + escrowId, + channelId, + meta.initState, + effects, + meta.user, + meta.node, + meta.approvedSignatureValidators ); emit EscrowDepositChallenged(escrowId, meta.initState, effects.newChallengeExpiry); } - function finalizeEscrowDeposit(bytes32 escrowId, State calldata candidate) external { + function finalizeEscrowDeposit(bytes32 channelId, bytes32 escrowId, State calldata candidate) external { + if (_isHomeChain(channelId)) { + // HOME CHAIN: Get user/node from channel definition + ChannelMeta storage channelMeta = _channels[channelId]; + _processHomeChainEscrowFinalize( + channelId, + candidate, + channelMeta.definition.user, + channelMeta.definition.node + ); + emit EscrowDepositFinalizedOnHome(escrowId, channelId, candidate); + return; + } + + // NON-HOME CHAIN: Use escrow metadata EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + require(meta.channelId == channelId, IncorrectChannelId()); // Validate consistency address user = meta.user; address node = meta.node; - - bool isHomeChain = _isHomeChain(meta.channelId); EscrowStatus status = meta.status; - if (!isHomeChain && status == EscrowStatus.DISPUTED && block.timestamp > meta.challengeExpireAt) { + if (status == EscrowStatus.DISPUTED && meta.challengeExpireAt < block.timestamp) { // NON-HOME CHAIN: Unilateral finalization after challenge timeout meta.status = EscrowStatus.FINALIZED; uint256 lockedAmount = meta.lockedAmount; meta.lockedAmount = 0; meta.challengeExpireAt = 0; - _pushFunds(node, meta.initState.nonHomeLedger.token, lockedAmount); + // Release to user as "deposit exchange" has not been signed yet (it is the "finalizeEscrowDeposit" state) + _pushFunds(0, user, meta.initState.nonHomeLedger.token, lockedAmount); - emit EscrowDepositFinalized(escrowId, meta.channelId, candidate); + emit EscrowDepositFinalized(escrowId, channelId, candidate); return; } require(candidate.intent == StateIntent.FINALIZE_ESCROW_DEPOSIT, IncorrectStateIntent()); - if (_isHomeChain(meta.channelId)) { - _processHomeChainEscrowFinalize(meta.channelId, candidate, user, node); - emit EscrowDepositFinalizedOnHome(escrowId, meta.channelId, candidate); - return; - } else { - // NON-HOME CHAIN: Update via EscrowDepositEngine - _validateSignatures(meta.channelId, candidate, user, node, meta.approvedSignatureValidators); + // NON-HOME CHAIN: Update via EscrowDepositEngine + _validateSignatures(channelId, candidate, user, node, meta.approvedSignatureValidators); - EscrowDepositEngine.TransitionContext memory ctx = - _buildEscrowDepositContext(escrowId, _nodeBalances[node][candidate.nonHomeLedger.token]); - EscrowDepositEngine.TransitionEffects memory effects = - EscrowDepositEngine.validateTransition(ctx, candidate); + EscrowDepositEngine.TransitionContext memory ctx = _buildEscrowDepositContext( + escrowId, + _nodeBalances[node][candidate.nonHomeLedger.token] + ); + EscrowDepositEngine.TransitionEffects memory effects = EscrowDepositEngine.validateTransition(ctx, candidate); - _applyEscrowDepositEffects( - escrowId, meta.channelId, candidate, effects, user, node, meta.approvedSignatureValidators - ); + _applyEscrowDepositEffects( + escrowId, + channelId, + candidate, + effects, + user, + node, + meta.approvedSignatureValidators + ); - emit EscrowDepositFinalized(escrowId, meta.channelId, candidate); - } + emit EscrowDepositFinalized(escrowId, channelId, candidate); } function initiateEscrowWithdrawal(ChannelDefinition calldata def, State calldata candidate) external { @@ -709,87 +861,126 @@ contract ChannelHub is IVault, ReentrancyGuard { bytes32 escrowId = Utils.getEscrowId(channelId, candidate.version); if (_isHomeChain(channelId)) { + // HOME CHAIN: Process through channel state, no escrow metadata _processHomeChainEscrowInitiate(channelId, candidate); emit EscrowWithdrawalInitiatedOnHome(escrowId, channelId, candidate); } else { // NON-HOME CHAIN EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, def.node); - EscrowWithdrawalEngine.TransitionEffects memory effects = - EscrowWithdrawalEngine.validateTransition(ctx, candidate); + EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateTransition( + ctx, + candidate + ); _applyEscrowWithdrawalEffects( - escrowId, channelId, candidate, effects, def.user, def.node, def.approvedSignatureValidators + escrowId, + channelId, + candidate, + effects, + def.user, + def.node, + def.approvedSignatureValidators ); emit EscrowWithdrawalInitiated(escrowId, channelId, candidate); } } - function challengeEscrowWithdrawal(bytes32 escrowId, bytes calldata challengerSig, ParticipantIndex challengerIdx) - external - { + function challengeEscrowWithdrawal( + bytes32 escrowId, + bytes calldata challengerSig, + ParticipantIndex challengerIdx + ) external { EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; - require(!_isHomeChain(meta.channelId), OnlyNonHomeEscrowsCanBeChallenged()); + bytes32 channelId = meta.channelId; + require(channelId != bytes32(0), NoChannelIdFoundForEscrow()); EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, meta.node); EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateChallenge(ctx); // Validate challenger signature - bytes32 channelId = meta.channelId; address user = meta.user; address node = meta.node; - (ISignatureValidator validator, bytes calldata sigData) = - _extractValidator(challengerSig, node, meta.approvedSignatureValidators); + (ISignatureValidator validator, bytes calldata sigData) = _extractValidator( + challengerSig, + node, + meta.approvedSignatureValidators + ); _validateChallengerSignature(channelId, meta.initState, sigData, validator, user, node, challengerIdx); _applyEscrowWithdrawalEffects( - escrowId, channelId, meta.initState, effects, user, node, meta.approvedSignatureValidators + escrowId, + channelId, + meta.initState, + effects, + user, + node, + meta.approvedSignatureValidators ); emit EscrowWithdrawalChallenged(escrowId, meta.initState, effects.newChallengeExpiry); } - function finalizeEscrowWithdrawal(bytes32 escrowId, State calldata candidate) external { + function finalizeEscrowWithdrawal(bytes32 channelId, bytes32 escrowId, State calldata candidate) external { + if (_isHomeChain(channelId)) { + // HOME CHAIN: Get user/node from channel definition + ChannelMeta storage channelMeta = _channels[channelId]; + _processHomeChainEscrowFinalize( + channelId, + candidate, + channelMeta.definition.user, + channelMeta.definition.node + ); + emit EscrowWithdrawalFinalizedOnHome(escrowId, channelId, candidate); + return; + } + + // NON-HOME CHAIN: Use escrow metadata EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; - bytes32 channelId = meta.channelId; + require(meta.channelId == channelId, IncorrectChannelId()); // Validate consistency address user = meta.user; address node = meta.node; - - bool isHomeChain = _isHomeChain(channelId); EscrowStatus status = meta.status; - if (!isHomeChain && status == EscrowStatus.DISPUTED && block.timestamp > meta.challengeExpireAt) { + if (status == EscrowStatus.DISPUTED && meta.challengeExpireAt < block.timestamp) { // NON-HOME CHAIN: Unilateral finalization after challenge timeout meta.status = EscrowStatus.FINALIZED; uint256 lockedAmount = meta.lockedAmount; meta.lockedAmount = 0; meta.challengeExpireAt = 0; - _pushFunds(node, meta.initState.nonHomeLedger.token, lockedAmount); + // Release locked amount back to node as "withdrawal exchange" has not been signed yet (it is the "finalizeEscrowWithdrawal" state) + address withdrawalToken = meta.initState.nonHomeLedger.token; + uint256 updatedWithdrawalBalance = _nodeBalances[node][withdrawalToken] + lockedAmount; + _nodeBalances[node][withdrawalToken] = updatedWithdrawalBalance; - emit EscrowWithdrawalFinalized(escrowId, meta.channelId, candidate); + emit NodeBalanceUpdated(node, withdrawalToken, 0, updatedWithdrawalBalance); + emit EscrowWithdrawalFinalized(escrowId, channelId, candidate); return; } require(candidate.intent == StateIntent.FINALIZE_ESCROW_WITHDRAWAL, IncorrectStateIntent()); - if (_isHomeChain(channelId)) { - _processHomeChainEscrowFinalize(channelId, candidate, user, node); - emit EscrowWithdrawalFinalizedOnHome(escrowId, channelId, candidate); - } else { - // Non-Home chain: Update via EscrowWithdrawalEngine - _validateSignatures(channelId, candidate, user, node, meta.approvedSignatureValidators); + // NON-HOME CHAIN: Update via EscrowWithdrawalEngine + _validateSignatures(channelId, candidate, user, node, meta.approvedSignatureValidators); - EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, node); - EscrowWithdrawalEngine.TransitionEffects memory effects = - EscrowWithdrawalEngine.validateTransition(ctx, candidate); + EscrowWithdrawalEngine.TransitionContext memory ctx = _buildEscrowWithdrawalContext(escrowId, node); + EscrowWithdrawalEngine.TransitionEffects memory effects = EscrowWithdrawalEngine.validateTransition( + ctx, + candidate + ); - _applyEscrowWithdrawalEffects( - escrowId, channelId, candidate, effects, user, node, meta.approvedSignatureValidators - ); + _applyEscrowWithdrawalEffects( + escrowId, + channelId, + candidate, + effects, + user, + node, + meta.approvedSignatureValidators + ); - emit EscrowWithdrawalFinalized(escrowId, channelId, candidate); - } + emit EscrowWithdrawalFinalized(escrowId, channelId, candidate); } function initiateMigration(ChannelDefinition calldata def, State calldata candidate) external { @@ -814,8 +1005,10 @@ contract ChannelHub is IVault, ReentrancyGuard { _userChannels[def.user].add(channelId); } - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][targetCandidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext( + channelId, + _nodeBalances[def.node][targetCandidate.homeLedger.token] + ); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, targetCandidate); _applyEffects(channelId, def, targetCandidate, effects); @@ -850,8 +1043,10 @@ contract ChannelHub is IVault, ReentrancyGuard { _userChannels[user].remove(channelId); } - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[def.node][targetCandidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext( + channelId, + _nodeBalances[def.node][targetCandidate.homeLedger.token] + ); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, targetCandidate); _applyEffects(channelId, def, targetCandidate, effects); @@ -871,11 +1066,17 @@ contract ChannelHub is IVault, ReentrancyGuard { address node, uint256 approvedSignatureValidators ) internal view { - (ISignatureValidator userValidator, bytes calldata userSigData) = - _extractValidator(state.userSig, node, approvedSignatureValidators); + (ISignatureValidator userValidator, bytes calldata userSigData) = _extractValidator( + state.userSig, + node, + approvedSignatureValidators + ); _validateSignature(channelId, state, userSigData, user, userValidator); - (ISignatureValidator nodeValidator, bytes calldata nodeSigData) = - _extractValidator(state.nodeSig, node, approvedSignatureValidators); + (ISignatureValidator nodeValidator, bytes calldata nodeSigData) = _extractValidator( + state.nodeSig, + node, + approvedSignatureValidators + ); _validateSignature(channelId, state, nodeSigData, node, nodeValidator); } @@ -899,11 +1100,11 @@ contract ChannelHub is IVault, ReentrancyGuard { require(ValidationResult.unwrap(result) != ValidationResult.unwrap(VALIDATION_FAILURE), IncorrectSignature()); } - function _extractValidator(bytes calldata signature, address node, uint256 approvedSignatureValidators) - internal - view - returns (ISignatureValidator validator, bytes calldata sigData) - { + function _extractValidator( + bytes calldata signature, + address node, + uint256 approvedSignatureValidators + ) internal view returns (ISignatureValidator validator, bytes calldata sigData) { require(signature.length > 0, EmptySignature()); uint8 validatorId = uint8(signature[0]); @@ -958,33 +1159,39 @@ contract ChannelHub is IVault, ReentrancyGuard { ChannelMeta storage meta = _channels[channelId]; ChannelDefinition memory metaDef = meta.definition; - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[metaDef.node][candidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext( + channelId, + _nodeBalances[metaDef.node][candidate.homeLedger.token] + ); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, metaDef, candidate, effects); } /// @dev Process HOME CHAIN path for escrow finalize operations - function _processHomeChainEscrowFinalize(bytes32 channelId, State calldata candidate, address user, address node) - internal - { + function _processHomeChainEscrowFinalize( + bytes32 channelId, + State calldata candidate, + address user, + address node + ) internal { ChannelMeta storage channelMeta = _channels[channelId]; ChannelDefinition memory channelDef = channelMeta.definition; _validateSignatures(channelId, candidate, user, node, channelDef.approvedSignatureValidators); - ChannelEngine.TransitionContext memory ctx = - _buildChannelContext(channelId, _nodeBalances[channelDef.node][candidate.homeLedger.token]); + ChannelEngine.TransitionContext memory ctx = _buildChannelContext( + channelId, + _nodeBalances[channelDef.node][candidate.homeLedger.token] + ); ChannelEngine.TransitionEffects memory effects = ChannelEngine.validateTransition(ctx, candidate); _applyEffects(channelId, channelDef, candidate, effects); } - function _buildChannelContext(bytes32 channelId, uint256 nodeBalance) - internal - view - returns (ChannelEngine.TransitionContext memory ctx) - { + function _buildChannelContext( + bytes32 channelId, + uint256 nodeBalance + ) internal view returns (ChannelEngine.TransitionContext memory ctx) { ChannelMeta storage meta = _channels[channelId]; ctx.status = meta.status; @@ -993,14 +1200,17 @@ contract ChannelHub is IVault, ReentrancyGuard { ctx.nodeAvailableFunds = nodeBalance; ctx.challengeExpiry = meta.challengeExpireAt; + address token = meta.lastState.homeLedger.token; + ctx.isParametricToken = isParametricToken[token]; + ctx.channelSubId = meta.subId; + return ctx; } - function _buildEscrowDepositContext(bytes32 escrowId, uint256 nodeAvailableFunds) - internal - view - returns (EscrowDepositEngine.TransitionContext memory ctx) - { + function _buildEscrowDepositContext( + bytes32 escrowId, + uint256 nodeAvailableFunds + ) internal view returns (EscrowDepositEngine.TransitionContext memory ctx) { EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; ctx.status = meta.status; @@ -1013,11 +1223,10 @@ contract ChannelHub is IVault, ReentrancyGuard { return ctx; } - function _buildEscrowWithdrawalContext(bytes32 escrowId, address node) - internal - view - returns (EscrowWithdrawalEngine.TransitionContext memory ctx) - { + function _buildEscrowWithdrawalContext( + bytes32 escrowId, + address node + ) internal view returns (EscrowWithdrawalEngine.TransitionContext memory ctx) { EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; ctx.status = meta.status; @@ -1047,14 +1256,12 @@ contract ChannelHub is IVault, ReentrancyGuard { meta.status = effects.newStatus; } - if (effects.clearDispute) { - meta.status = ChannelStatus.OPERATING; - meta.challengeExpireAt = 0; + if (meta.challengeExpireAt != effects.newChallengeExpiry) { + meta.challengeExpireAt = effects.newChallengeExpiry; } if (effects.closeChannel) { meta.lockedFunds = 0; - meta.challengeExpireAt = 0; } } @@ -1075,32 +1282,50 @@ contract ChannelHub is IVault, ReentrancyGuard { // Process POSITIVE deltas first (additions to lockedFunds) to prevent underflow if (effects.userFundsDelta > 0) { uint256 amount = effects.userFundsDelta.toUint256(); - _pullFunds(def.user, token, amount); + _pullFunds(def.user, meta.subId, token, amount); meta.lockedFunds += amount; } if (effects.nodeFundsDelta > 0) { uint256 amount = effects.nodeFundsDelta.toUint256(); - _nodeBalances[def.node][token] -= amount; + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + uint256 updatedBalance = nodeBalance - amount; + if (!isParametricToken[token]) { + _nodeBalances[def.node][token] = updatedBalance; + } else { + _nodeSubBalances[def.node][token][meta.subId] = updatedBalance; + } + meta.lockedFunds += amount; + + emit NodeBalanceUpdated(def.node, token, meta.subId, updatedBalance); } // Then process NEGATIVE deltas (subtractions from lockedFunds) if (effects.userFundsDelta < 0) { uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(def.user, token, amount); + _pushFunds(meta.subId, def.user, token, amount); meta.lockedFunds -= amount; } if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[def.node][token] += amount; + uint256 nodeBalance = _getNodeBalance(def.node, candidate.homeLedger.token, meta.subId); + uint256 updatedBalance = nodeBalance + amount; + if (!isParametricToken[token]) { + _nodeBalances[def.node][token] = updatedBalance; + } else { + _nodeSubBalances[def.node][token][meta.subId] = updatedBalance; + } + meta.lockedFunds -= amount; + + emit NodeBalanceUpdated(def.node, token, meta.subId, updatedBalance); } // Special handling for CLOSE: push nodeAllocation directly to node address if (effects.closeChannel && candidate.homeLedger.nodeAllocation > 0) { - _pushFunds(def.node, token, candidate.homeLedger.nodeAllocation); + _pushFunds(meta.subId, def.node, token, candidate.homeLedger.nodeAllocation); meta.lockedFunds -= candidate.homeLedger.nodeAllocation; } @@ -1124,11 +1349,7 @@ contract ChannelHub is IVault, ReentrancyGuard { } if (effects.updateInitState) { - meta.initState = candidate; - meta.channelId = channelId; - meta.user = user; - meta.node = node; - meta.approvedSignatureValidators = approvedSignatureValidators; + _initEscrowDepositMetadata(escrowId, channelId, candidate, user, node, approvedSignatureValidators); } if (effects.newUnlockAt > 0) { @@ -1145,23 +1366,27 @@ contract ChannelHub is IVault, ReentrancyGuard { // Handle user funds (positive = pull from user) if (effects.userFundsDelta > 0) { uint256 amount = effects.userFundsDelta.toUint256(); - _pullFunds(user, token, amount); + _pullFunds(user, 0, token, amount); meta.lockedAmount += amount; } else if (effects.userFundsDelta < 0) { uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(user, token, amount); + _pushFunds(0, user, token, amount); meta.lockedAmount -= amount; } // Handle node funds (positive = pull from node vault, negative = release to vault) if (effects.nodeFundsDelta > 0) { uint256 amount = effects.nodeFundsDelta.toUint256(); - _nodeBalances[node][token] -= amount; + uint256 updatedBalance = _nodeBalances[node][token] - amount; + _nodeBalances[node][token] = updatedBalance; meta.lockedAmount += amount; + emit NodeBalanceUpdated(node, token, 0, updatedBalance); } else if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[node][token] += amount; + uint256 updatedBalance = _nodeBalances[node][token] + amount; + _nodeBalances[node][token] = updatedBalance; meta.lockedAmount -= amount; + emit NodeBalanceUpdated(node, token, 0, updatedBalance); } // NOTE: purge escrow deposits to unlock unutilized node liquidity @@ -1184,11 +1409,7 @@ contract ChannelHub is IVault, ReentrancyGuard { } if (effects.updateInitState) { - meta.initState = candidate; - meta.channelId = channelId; - meta.user = user; - meta.node = node; - meta.approvedSignatureValidators = approvedSignatureValidators; + _initEscrowWithdrawalMetadata(escrowId, channelId, candidate, user, node, approvedSignatureValidators); } if (effects.newChallengeExpiry > 0) { @@ -1201,29 +1422,65 @@ contract ChannelHub is IVault, ReentrancyGuard { // Handle user funds (negative = push to user) if (effects.userFundsDelta > 0) { uint256 amount = effects.userFundsDelta.toUint256(); - _pullFunds(user, token, amount); + _pullFunds(user, 0, token, amount); meta.lockedAmount += amount; } else if (effects.userFundsDelta < 0) { uint256 amount = (-effects.userFundsDelta).toUint256(); - _pushFunds(user, token, amount); + _pushFunds(0, user, token, amount); meta.lockedAmount -= amount; } // Handle node funds (positive = pull from node vault, negative = release to vault) if (effects.nodeFundsDelta > 0) { uint256 amount = effects.nodeFundsDelta.toUint256(); - _nodeBalances[node][token] -= amount; + uint256 updatedBalance = _nodeBalances[node][token] - amount; + _nodeBalances[node][token] = updatedBalance; meta.lockedAmount += amount; + emit NodeBalanceUpdated(node, token, 0, updatedBalance); } else if (effects.nodeFundsDelta < 0) { uint256 amount = (-effects.nodeFundsDelta).toUint256(); - _nodeBalances[node][token] += amount; + uint256 updatedBalance = _nodeBalances[node][token] + amount; + _nodeBalances[node][token] = updatedBalance; meta.lockedAmount -= amount; + emit NodeBalanceUpdated(node, token, 0, updatedBalance); } // NOTE: purge escrow deposits to unlock unutilized node liquidity _purgeEscrowDeposits(); } + function _initEscrowDepositMetadata( + bytes32 escrowId, + bytes32 channelId, + State memory candidate, + address user, + address node, + uint256 approvedSignatureValidators + ) internal { + EscrowDepositMeta storage meta = _escrowDeposits[escrowId]; + meta.channelId = channelId; + meta.initState = candidate; + meta.user = user; + meta.node = node; + meta.approvedSignatureValidators = approvedSignatureValidators; + } + + function _initEscrowWithdrawalMetadata( + bytes32 escrowId, + bytes32 channelId, + State memory candidate, + address user, + address node, + uint256 approvedSignatureValidators + ) internal { + EscrowWithdrawalMeta storage meta = _escrowWithdrawals[escrowId]; + meta.channelId = channelId; + meta.initState = candidate; + meta.user = user; + meta.node = node; + meta.approvedSignatureValidators = approvedSignatureValidators; + } + function _requireValidDefinition(ChannelDefinition calldata def) internal pure { require(def.user != address(0), InvalidAddress()); require(def.node != address(0), InvalidAddress()); @@ -1240,7 +1497,7 @@ contract ChannelHub is IVault, ReentrancyGuard { return _channels[channelId].lastState.homeLedger.chainId == block.chainid; } - function _pullFunds(address from, address token, uint256 amount) internal nonReentrant { + function _pullFunds(address from, uint48 toSubId, address token, uint256 amount) internal nonReentrant { if (amount == 0) return; if (token == address(0)) { @@ -1250,38 +1507,71 @@ contract ChannelHub is IVault, ReentrancyGuard { } if (token != address(0)) { - IERC20(token).safeTransferFrom(from, address(this), amount); + if (!isParametricToken[token]) { + // Non-parametric token + IERC20(token).safeTransferFrom(from, address(this), amount); + } else { + // Parametric token with sub-account + IParametricToken(token).approvedTransferToSub(from, address(this), toSubId, amount); + } } } - function _pushFunds(address to, address token, uint256 amount) internal nonReentrant { + function _pushFunds(uint48 fromSubId, address to, address token, uint256 amount) internal nonReentrant { if (amount == 0) return; if (token == address(0)) { // Native token: limit gas to prevent depletion attacks - (bool success,) = payable(to).call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); + (bool success, ) = payable(to).call{value: amount, gas: TRANSFER_GAS_LIMIT}(""); if (!success) { - _reclaims[to][token] += amount; - emit TransferFailed(to, token, amount); + if (!isParametricToken[token]) { + _reclaims[to][token] += amount; + } else { + _subReclaims[to][token][fromSubId] += amount; + } + emit TransferFailed(fromSubId, to, token, amount); return; } } else { - // ERC20: Use balance-checking approach for maximum robustness - uint256 balanceBefore = IERC20(token).balanceOf(address(this)); + if (!isParametricToken[token]) { + // ERC20: Use balance-checking approach for maximum robustness + uint256 balanceBefore = IERC20(token).balanceOf(address(this)); + + // limit gas to prevent depletion attacks + (bool success, ) = address(token).call{gas: TRANSFER_GAS_LIMIT}( + abi.encodeCall(IERC20.transfer, (to, amount)) + ); + + uint256 balanceAfter = IERC20(token).balanceOf(address(this)); + + // Success criteria: call succeeded AND sufficient balance AND balance decreased by exactly the expected amount + // Check balanceBefore >= amount first to prevent underflow revert + bool transferSucceeded = success && balanceBefore >= amount && balanceAfter == balanceBefore - amount; + + if (!transferSucceeded) { + _reclaims[to][token] += amount; + emit TransferFailed(fromSubId, to, token, amount); + } + } else { + // ERC20: Use balance-checking approach for maximum robustness + uint256 subBalanceBefore = IParametricToken(token).balanceOfSub(address(this), fromSubId); - // limit gas to prevent depletion attacks - (bool success,) = - address(token).call{gas: TRANSFER_GAS_LIMIT}(abi.encodeCall(IERC20.transfer, (to, amount))); + // limit gas to prevent depletion attacks + (bool success, ) = address(token).call{gas: TRANSFER_GAS_LIMIT}( + abi.encodeCall(IParametricToken.transferFromSub, (fromSubId, to, amount)) + ); - uint256 balanceAfter = IERC20(token).balanceOf(address(this)); + uint256 subBalanceAfter = IParametricToken(token).balanceOfSub(address(this), fromSubId); - // Success criteria: call succeeded AND sufficient balance AND balance decreased by exactly the expected amount - // Check balanceBefore >= amount first to prevent underflow revert - bool transferSucceeded = success && balanceBefore >= amount && balanceAfter == balanceBefore - amount; + // Success criteria: call succeeded AND sufficient balance AND balance decreased by exactly the expected amount + // Check balanceBefore >= amount first to prevent underflow revert + bool transferSucceeded = + success && subBalanceBefore >= amount && subBalanceAfter == subBalanceBefore - amount; - if (!transferSucceeded) { - _reclaims[to][token] += amount; - emit TransferFailed(to, token, amount); + if (!transferSucceeded) { + _subReclaims[to][token][fromSubId] += amount; + emit TransferFailed(fromSubId, to, token, amount); + } } } } diff --git a/contracts/src/EscrowDepositEngine.sol b/contracts/src/EscrowDepositEngine.sol index cb3ca756e..4d4d4d88f 100644 --- a/contracts/src/EscrowDepositEngine.sol +++ b/contracts/src/EscrowDepositEngine.sol @@ -23,6 +23,7 @@ library EscrowDepositEngine { error IncorrectEscrowStatus(); error EscrowAlreadyExists(); error EscrowAlreadyFinalized(); + error ChallengeExpired(); error IncorrectUserAllocation(); error UserAllocationAndNetFlowMismatch(); @@ -128,6 +129,11 @@ library EscrowDepositEngine { require(netFlowsSum >= 0, NegativeNetFlowSum()); require(allocsSum == netFlowsSum.toUint256(), InvalidAllocationSum()); + + // If channel is DISPUTED, check that challenge hasn't expired + if (ctx.status == EscrowStatus.DISPUTED) { + require(block.timestamp <= ctx.challengeExpiry, ChallengeExpired()); + } } // ========== Internal: Phase 2 - Intent-Specific Calculation ========== diff --git a/contracts/src/EscrowWithdrawalEngine.sol b/contracts/src/EscrowWithdrawalEngine.sol index b32cee4ab..fa9ffc5d6 100644 --- a/contracts/src/EscrowWithdrawalEngine.sol +++ b/contracts/src/EscrowWithdrawalEngine.sol @@ -22,6 +22,7 @@ library EscrowWithdrawalEngine { error IncorrectEscrowStatus(); error EscrowAlreadyExists(); error EscrowAlreadyFinalized(); + error ChallengeExpired(); error IncorrectHomeChain(); error IncorrectNonHomeChain(); @@ -124,6 +125,11 @@ library EscrowWithdrawalEngine { require(netFlowsSum >= 0, NegativeNetFlowSum()); require(allocsSum == netFlowsSum.toUint256(), InvalidAllocationSum()); + + // If channel is DISPUTED, check that challenge hasn't expired + if (ctx.status == EscrowStatus.DISPUTED) { + require(block.timestamp <= ctx.challengeExpiry, ChallengeExpired()); + } } // ========== Internal: Phase 2 - Intent-Specific Calculation ========== diff --git a/contracts/src/ParametricToken.sol b/contracts/src/ParametricToken.sol new file mode 100644 index 000000000..f20af60c0 --- /dev/null +++ b/contracts/src/ParametricToken.sol @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./interfaces/IParametricToken.sol"; +import "forge-std/console.sol"; + +contract ParametricToken is ERC20, IParametricToken { + uint8 public constant NUMBER_OF_PARAMETERS = 1; + + struct Account { + AccountType accountType; + uint256 balance; + uint64[NUMBER_OF_PARAMETERS] parameters; + } + + struct ParamConfig { + bytes32 name; + uint8 decimals; + bool isMutable; + } + + struct SubAccount { + uint256 balance; + uint64[NUMBER_OF_PARAMETERS] parameters; + } + + struct SuperAccount { + SubAccount[] subs; + uint48 subsCount; + } + + struct Allowance { + uint256 total; + uint256 sub; + uint48 subId; + } + + ParamConfig[NUMBER_OF_PARAMETERS] public PARAM_CONFIG; + uint64 constant IMMUTABLE_PARAMETER = 1; + + mapping(address => Account) private _accounts; + mapping(address => SuperAccount) private _supers; + mapping(address => mapping(address => Allowance)) private _allowances; + + uint64[NUMBER_OF_PARAMETERS] private _parametersInit; + + modifier onlyNormal(address account) { + require(_accounts[account].accountType == AccountType.Normal, "Not a normal account"); + _; + } + + modifier onlySuper(address account) { + require(_accounts[account].accountType == AccountType.Super, "Not a super account"); + _; + } + + modifier onlyValidSub(address account, uint48 subId) { + require(_accounts[account].accountType == AccountType.Super, "Not a super account"); + require(subId < _supers[account].subsCount, "Sub-account doesn't exist"); + _; + } + + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { + PARAM_CONFIG = [ParamConfig({name: bytes32("myParam"), decimals: 0, isMutable: true})]; + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + _parametersInit[i] = 0; + } + } + + // ========== ERC20 Overrides ========== + + function transfer(address to, uint256 amount) public override(ERC20, IERC20) returns (bool) { + address from = _msgSender(); + + Account storage fromAcc = _accounts[from]; + Account storage toAcc = _accounts[to]; + + require(_noParamsConflict(from, 0, to, 0), "Conflict of parameters"); + + if (fromAcc.accountType == AccountType.Normal && toAcc.accountType == AccountType.Normal) { + // Normal transfer + bool success = super.transfer(to, amount); + if (success) { + fromAcc.balance -= amount; + toAcc.balance += amount; + } + return success; + } + + revert("Standard transfer not allowed for super accounts"); + } + + function transferFrom(address from, address to, uint256 amount) public override(ERC20, IERC20) returns (bool) { + Account storage fromAcc = _accounts[from]; + Account storage toAcc = _accounts[to]; + + require(_noParamsConflict(from, 0, to, 0), "Conflict of parameters"); + + if (fromAcc.accountType == AccountType.Normal && toAcc.accountType == AccountType.Normal) { + // Check allowance + uint256 allowed = _allowances[from][_msgSender()].total; + require(allowed >= amount, "Insufficient allowance"); + + bool success = super.transferFrom(from, to, amount); + if (success) { + fromAcc.balance -= amount; + toAcc.balance += amount; + _allowances[from][_msgSender()].total -= amount; + } + return success; + } + + revert("TransferFrom not allowed for super accounts"); + } + + function allowance(address owner, address spender) public view override(ERC20, IERC20) returns (uint256) { + return _allowances[owner][spender].total; + } + + function approve(address spender, uint256 amount) public override(ERC20, IERC20) returns (bool) { + address owner = _msgSender(); + + _allowances[owner][spender].total = amount; + + if (_accounts[owner].accountType == AccountType.Super && _allowances[owner][spender].sub > amount) { + _allowances[owner][spender].sub = amount; + } + + super.approve(spender, amount); + + emit Approval(owner, spender, amount); + return true; + } + + // ========== Account Queries ========== + + function name() public view override(ERC20) returns (string memory) { + return super.name(); + } + + function symbol() public view override(ERC20) returns (string memory) { + return super.symbol(); + } + + function decimals() public view override(ERC20) returns (uint8) { + return super.decimals(); + } + + function totalSupply() public view override(ERC20, IERC20) returns (uint256) { + return super.totalSupply(); + } + + function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) { + return _accounts[account].balance; + } + + // ========== Account Management ========== + + function accountType(address account) external view returns (AccountType) { + return _accounts[account].accountType; + } + + function convertToSuper(address account) external onlyNormal(account) returns (bool) { + require(_msgSender() == account, "Only owner can convert"); + + Account storage acc = _accounts[account]; + + // Convert to super account + acc.accountType = AccountType.Super; + + // Create subId 0 with current balance + _supers[account].subs.push(SubAccount({balance: acc.balance, parameters: acc.parameters})); + _supers[account].subsCount = 1; + + // Clear normal parameters + acc.parameters = _parametersInit; + + emit AccountConvertedToSuper(account); + emit SubAccountCreated(account, 0); + + return true; + } + + function createSubAccount(address account) external onlySuper(account) returns (uint48) { + require(_msgSender() == account, "Only owner can create"); + + SuperAccount storage acc = _supers[account]; + + acc.subs.push(SubAccount({balance: 0, parameters: _parametersInit})); + acc.subsCount = uint48(acc.subs.length); + uint48 newSubId = acc.subsCount - 1; + + emit SubAccountCreated(account, newSubId); + + return newSubId; + } + + // ========== Sub-account Queries ========== + + function balanceOfSub(address account, uint48 subId) external view onlySuper(account) returns (uint256) { + require(subId < _supers[account].subsCount, "Sub-account doesn't exist"); + return _supers[account].subs[subId].balance; + } + + function subsCountOf(address account) external view onlySuper(account) returns (uint48) { + return _supers[account].subsCount; + } + + function numberOfParameters() external pure returns (uint8) { + return NUMBER_OF_PARAMETERS; + } + + function parameterOf(uint8 paramIndex, address account) external view onlyNormal(account) returns (uint64) { + require(paramIndex < NUMBER_OF_PARAMETERS, "Index exceeds number of parameters"); + return _accounts[account].parameters[paramIndex]; + } + + function parameterOfSub( + uint8 paramIndex, + address account, + uint48 subId + ) external view onlyValidSub(account, subId) returns (uint64) { + require(paramIndex < NUMBER_OF_PARAMETERS, "Index exceeds number of parameters"); + return _supers[account].subs[subId].parameters[paramIndex]; + } + + // ========== Allowances ========== + + function allowanceForSub( + address owner, + uint48 subId, + address spender + ) external view onlyValidSub(owner, subId) returns (uint256) { + Allowance storage al = _allowances[owner][spender]; + if (al.subId == subId) { + return al.sub; + } + return al.total - al.sub; + } + + function approveForSub(uint48 ownerSubId, address spender, uint256 amount) external returns (bool) { + address owner = _msgSender(); + Account storage acc = _accounts[owner]; + require(acc.accountType == AccountType.Super, "Not a super account"); + require(ownerSubId < _supers[owner].subsCount, "Sub-account doesn't exist"); + + Allowance storage al = _allowances[owner][spender]; + + al.subId = ownerSubId; + al.sub = amount; + + // Adjust total if needed + if (amount > al.total) { + al.total = amount; // Total becomes at least the sub-amount + } + + emit ApprovalForSub(owner, ownerSubId, spender, amount); + + return true; + } + + // Helper to check and consume allowance for a specific subId + function _sufficientAllowanceForSub( + address owner, + address spender, + uint48 fromSubId, + uint256 amount + ) internal view onlyValidSub(owner, fromSubId) returns (bool) { + Allowance storage al = _allowances[owner][spender]; + return fromSubId == al.subId ? al.sub >= amount : al.total - al.sub >= amount; + } + + function _consumeAllowanceForSub( + address owner, + address spender, + uint48 fromSubId, + uint256 amount + ) internal onlyValidSub(owner, fromSubId) { + Allowance storage al = _allowances[owner][spender]; + + if (fromSubId == al.subId) { + // Use sub-allowance first + require(al.sub >= amount, "Insufficient sub-allowance"); + al.sub -= amount; + al.total -= amount; + } else { + // Use from remaining total allowance (total - sub) + uint256 remaining = al.total - al.sub; + require(remaining >= amount, "Insufficient allowance for this subId"); + al.total -= amount; + } + } + + function _noParamsConflict(address from, uint48 fromSubId, address to, uint48 toSubId) private view returns (bool) { + uint64[NUMBER_OF_PARAMETERS] memory fromParams; + uint64[NUMBER_OF_PARAMETERS] memory toParams; + + if (_accounts[from].balance == 0 || _accounts[to].balance == 0) return true; + + if (_accounts[from].accountType == AccountType.Normal) { + fromParams = _accounts[from].parameters; + } else { + require(fromSubId < _supers[from].subsCount, "Subaccount doesn't exist"); + fromParams = _supers[from].subs[fromSubId].parameters; + } + + if (_accounts[to].accountType == AccountType.Normal) { + toParams = _accounts[to].parameters; + } else { + require(toSubId < _supers[to].subsCount, "Subaccount doesn't exist"); + toParams = _supers[to].subs[toSubId].parameters; + } + + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (!PARAM_CONFIG[i].isMutable && fromParams[i] != toParams[i]) return false; + } + + return true; + } + + function _weightedAverage( + uint64 param1, + uint256 amount1, + uint64 param2, + uint256 amount2 + ) private pure returns (uint64) { + require(amount1 > 0 && amount2 > 0, "Invalid amounts"); + uint256 sumProduct = uint256(param1) * amount1 + uint256(param2) * amount2; + uint256 sum = amount1 + amount2; + + return uint64(sumProduct / sum); + } + + // ========== Transfers ========== + + function transferToSub( + address toSuper, + uint48 toSubId, + uint256 amount + ) external onlyValidSub(toSuper, toSubId) returns (bool) { + address from = _msgSender(); + Account storage fromAcc = _accounts[from]; + Account storage toAcc = _accounts[toSuper]; + + require(amount > 0, "Void amount"); + require(fromAcc.accountType == AccountType.Normal, "Sender must be normal"); + + require(_noParamsConflict(from, 0, toSuper, toSubId), "Conflict of parameters"); + require(fromAcc.balance >= amount, "Insufficient balance"); + fromAcc.balance -= amount; + + SubAccount storage toSubAcc = _supers[toSuper].subs[toSubId]; + uint256 oldSubBalance = toSubAcc.balance; + toSubAcc.balance += amount; + toAcc.balance += amount; + + // Update toSubAcc parameters + if (oldSubBalance > 0) { + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + toSubAcc.parameters[i] = _weightedAverage( + toSubAcc.parameters[i], + oldSubBalance, + fromAcc.parameters[i], + amount + ); + } + } + } else { + toSubAcc.parameters = fromAcc.parameters; + } + + // Update fromAcc parameters + if (fromAcc.balance == 0) fromAcc.parameters = _parametersInit; + + emit TransferToSub(from, toSuper, toSubId, amount); + emit Transfer(from, toSuper, amount); + + return true; + } + + function transferFromSub(uint48 fromSubId, address to, uint256 amount) external returns (bool) { + address fromSuper = _msgSender(); + Account storage fromAcc = _accounts[fromSuper]; + Account storage toAcc = _accounts[to]; + + require(amount > 0, "Void amount"); + require(fromAcc.accountType == AccountType.Super, "Not a super account"); + SuperAccount storage fromSuperAcc = _supers[fromSuper]; + require(toAcc.accountType == AccountType.Normal, "Recipient must be normal"); + require(fromSubId < fromSuperAcc.subsCount, "Sub-account doesn't exist"); + + require(_noParamsConflict(fromSuper, fromSubId, to, 0), "Conflict of parameters"); + require(fromSuperAcc.subs[fromSubId].balance >= amount, "Insufficient balance"); + fromSuperAcc.subs[fromSubId].balance -= amount; + fromAcc.balance -= amount; + + uint256 oldToBalance = toAcc.balance; + toAcc.balance += amount; + + // Update toAcc parameters + if (oldToBalance > 0) { + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + toAcc.parameters[i] = _weightedAverage( + toAcc.parameters[i], + oldToBalance, + fromSuperAcc.subs[fromSubId].parameters[i], + amount + ); + } + } + } else { + toAcc.parameters = fromSuperAcc.subs[fromSubId].parameters; + } + + // Update fromAcc parameters + if (fromAcc.balance == 0) fromSuperAcc.subs[fromSubId].parameters = _parametersInit; + + emit TransferFromSub(fromSuper, fromSubId, to, amount); + emit Transfer(fromSuper, to, amount); + + return true; + } + + function transferBetweenSubs(uint48 fromSubId, uint48 toSubId, uint256 amount) external returns (bool) { + address superAccount = _msgSender(); + Account storage acc = _accounts[superAccount]; + + require(acc.accountType == AccountType.Super, "Not a super account"); + SuperAccount storage superAcc = _supers[superAccount]; + + require(fromSubId < superAcc.subsCount && toSubId < superAcc.subsCount, "Sub-account doesn't exist"); + require(_noParamsConflict(superAccount, fromSubId, superAccount, toSubId), "Conflict of parameters"); + + require(superAcc.subs[fromSubId].balance >= amount, "Insufficient balance"); + + // Update fromSub + superAcc.subs[fromSubId].balance -= amount; + + // Update toSub + uint256 oldSubBalance = superAcc.subs[toSubId].balance; + superAcc.subs[toSubId].balance += amount; + + // Update toSubId parameters + if (oldSubBalance > 0) { + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + superAcc.subs[toSubId].parameters[i] = _weightedAverage( + superAcc.subs[toSubId].parameters[i], + oldSubBalance, + superAcc.subs[fromSubId].parameters[i], + amount + ); + } + } + } else { + superAcc.subs[toSubId].parameters = superAcc.subs[fromSubId].parameters; + } + + // Update fromSubId parameters + if (superAcc.subs[fromSubId].balance == 0) superAcc.subs[fromSubId].parameters = _parametersInit; + + emit TransferBetweenSubs(superAccount, fromSubId, toSubId, amount); + + return true; + } + + // ========== Approved Transfers ========== + + function approvedTransferToSub( + address from, + address toSuper, + uint48 toSubId, + uint256 amount + ) external onlyValidSub(toSuper, toSubId) returns (bool) { + address spender = _msgSender(); + + // Execute transfer + Account storage fromAcc = _accounts[from]; + Allowance storage al = _allowances[from][spender]; + + require(amount > 0, "Void amount"); + require(fromAcc.accountType == AccountType.Normal, "From must be normal"); + require(_noParamsConflict(from, 0, toSuper, toSubId), "Conflict of parameters"); + require(fromAcc.balance >= amount, "Insufficient balance"); + require(al.total >= amount, "Insufficient allowance"); + fromAcc.balance -= amount; + + SubAccount storage toSubAcc = _supers[toSuper].subs[toSubId]; + uint256 oldSubBalance = toSubAcc.balance; + toSubAcc.balance += amount; + _accounts[toSuper].balance += amount; + + // Update toSubAcc parameters + if (oldSubBalance > 0) { + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + toSubAcc.parameters[i] = _weightedAverage( + toSubAcc.parameters[i], + oldSubBalance, + fromAcc.parameters[i], + amount + ); + } + } + } else { + toSubAcc.parameters = fromAcc.parameters; + } + + // Update fromAcc parameters + if (fromAcc.balance == 0) fromAcc.parameters = _parametersInit; + + // Consume allowance + al.total -= amount; + + emit TransferToSub(from, toSuper, toSubId, amount); + emit Transfer(from, toSuper, amount); + + return true; + } + + function approvedTransferFromSubToSub( + address fromSuper, + uint48 fromSubId, + address toSuper, + uint48 toSubId, + uint256 amount + ) external onlyValidSub(fromSuper, fromSubId) onlyValidSub(toSuper, toSubId) returns (bool) { + address spender = _msgSender(); + + // Execute transfer from sub to sub + SuperAccount storage fromSuperAcc = _supers[fromSuper]; + + require(amount > 0, "Void amount"); + require(fromSuperAcc.subs[fromSubId].balance >= amount, "Insufficient balance"); + require(_sufficientAllowanceForSub(fromSuper, spender, fromSubId, amount), "Insufficient allowance"); + + fromSuperAcc.subs[fromSubId].balance -= amount; + _accounts[fromSuper].balance -= amount; + + SubAccount storage toSubAcc = _supers[toSuper].subs[toSubId]; + uint256 oldSubBalance = toSubAcc.balance; + toSubAcc.balance += amount; + _accounts[toSuper].balance += amount; + + // Update toSubAcc parameters + if (oldSubBalance > 0) { + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + toSubAcc.parameters[i] = _weightedAverage( + toSubAcc.parameters[i], + oldSubBalance, + fromSuperAcc.subs[fromSubId].parameters[i], + amount + ); + } + } + } else { + toSubAcc.parameters = fromSuperAcc.subs[fromSubId].parameters; + } + + // Check and consume allowance + _consumeAllowanceForSub(fromSuper, spender, fromSubId, amount); + + emit TransferFromSubToSub(fromSuper, fromSubId, toSuper, toSubId, amount); + if (fromSuper != toSuper) emit Transfer(fromSuper, toSuper, amount); + + return true; + } + + // ========== Mint/Burn Helpers ========== + + function mint(uint256 amount) external { + require(amount > 0, "Void amount"); + address to = _msgSender(); + _mintParametric(to, amount); + } + + // function _mintParametric(address account, uint256 amount) internal { + // super._mint(account, amount); + // _accounts[account].balance += amount; + // // parameter logic... + // } + + function _mintParametric(address account, uint256 amount) internal { + console.log(">> _mintParametric called"); + console.log(">> account:", account); + + Account storage acc = _accounts[account]; + + if (acc.accountType == AccountType.Super) { + // Mint to sub-account 0 + SuperAccount storage superAcc = _supers[account]; + require(superAcc.subsCount > 0, "No sub-accounts"); + + SubAccount storage sub0 = superAcc.subs[0]; + + // Calculate new weighted average parameters + uint256 oldBalance = sub0.balance; + + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + if (oldBalance == 0) { + // First mint to this sub - set to block.timestamp + sub0.parameters[i] = uint64(block.timestamp); + } else { + // Weighted average for non-zero + sub0.parameters[i] = _weightedAverage( + sub0.parameters[i], + oldBalance, + uint64(block.timestamp), + amount + ); + } + } else { + // Immutable parameter + if (oldBalance == 0) { + // First mint - set to constant + sub0.parameters[i] = IMMUTABLE_PARAMETER; + } + // If oldBalance > 0, immutable parameter stays as is (no change) + } + } + + // Update balances + sub0.balance += amount; + acc.balance += amount; + + super._mint(account, amount); + } else { + // Normal account + uint256 oldBalance = acc.balance; + + for (uint256 i = 0; i < NUMBER_OF_PARAMETERS; i++) { + if (PARAM_CONFIG[i].isMutable) { + if (oldBalance == 0) { + // First mint - set to block.timestamp + acc.parameters[i] = uint64(block.timestamp); + } else { + // Non-zero balance + acc.parameters[i] = _weightedAverage( + acc.parameters[i], + oldBalance, + uint64(block.timestamp), + amount + ); + } + } else { + // Immutable parameter + if (oldBalance == 0) { + // First mint - set to constant + acc.parameters[i] = IMMUTABLE_PARAMETER; + } + // If oldBalance > 0, immutable parameter stays as is + } + } + + acc.balance += amount; + super._mint(account, amount); + } + } + + // function _burn(address account, uint256 amount) internal { + // super._burn(account, amount); + // // Your custom logic here + // _accounts[account].balance -= amount; + // } + + function _burnParametric(address account, uint256 amount) internal { + Account storage acc = _accounts[account]; + require(amount > 0, "Void amount"); + require(account == _msgSender(), "Burn allowed only from own account"); + + if (acc.accountType == AccountType.Super) { + // Burn from sub-account 0 + SuperAccount storage superAcc = _supers[account]; + require(superAcc.subsCount > 0, "No sub-accounts"); + + SubAccount storage sub0 = superAcc.subs[0]; + require(sub0.balance >= amount, "Insufficient balance in sub-account 0"); + + // Calculate new balance after burn + uint256 newBalance = sub0.balance - amount; + + // Update parameters if balance becomes zero + if (newBalance == 0) sub0.parameters = _parametersInit; + + // Note: When balance > 0 after burn, parameters remain unchanged + // because burning doesn't introduce new tokens with different parameters + + // Update balances + sub0.balance = newBalance; + acc.balance -= amount; + + super._burn(account, amount); + } else { + // Normal account + require(acc.balance >= amount, "Insufficient balance"); + + uint256 newBalance = acc.balance - amount; + + // Update parameters if balance becomes zero + if (newBalance == 0) acc.parameters = _parametersInit; + + // Note: When balance > 0 after burn, parameters remain unchanged + + acc.balance = newBalance; + super._burn(account, amount); + } + } +} diff --git a/contracts/src/PremintERC20.sol b/contracts/src/PremintERC20.sol index c4f7c8026..5277c6db7 100644 --- a/contracts/src/PremintERC20.sol +++ b/contracts/src/PremintERC20.sol @@ -1,15 +1,18 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.22; -import {ERC20, ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; +import { ERC20, ERC20Capped } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; contract PremintERC20 is ERC20Capped { uint8 private immutable DECIMALS; - constructor(string memory name, string memory symbol, uint8 decimals_, address beneficiary, uint256 cap) - ERC20(name, symbol) - ERC20Capped(cap) - { + constructor( + string memory name, + string memory symbol, + uint8 decimals_, + address beneficiary, + uint256 cap + ) ERC20(name, symbol) ERC20Capped(cap) { DECIMALS = decimals_; _mint(beneficiary, cap); } diff --git a/contracts/src/interfaces/IParametricToken.sol b/contracts/src/interfaces/IParametricToken.sol new file mode 100644 index 000000000..e07ddb436 --- /dev/null +++ b/contracts/src/interfaces/IParametricToken.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title IParametricToken + * @dev Extension of ERC20 that allows a single address to manage multiple + * sub-accounts (partitions), each with its own parameters (e.g., mint time) + */ +interface IParametricToken is IERC20 { + // Account types + enum AccountType { + Normal, + Super + } + + // Events + event AccountConvertedToSuper(address indexed account); + event SubAccountCreated(address indexed superAccount, uint48 indexed subId); + event TransferToSub(address indexed from, address indexed toSuper, uint48 indexed toSubId, uint256 amount); + event TransferFromSub(address indexed fromSuper, uint48 indexed fromSubId, address indexed to, uint256 amount); + event TransferBetweenSubs( + address indexed superAccount, uint48 indexed fromSubId, uint48 indexed toSubId, uint256 amount + ); + event TransferFromSubToSub( + address indexed fromSuper, uint48 indexed fromSubId, address indexed toSuper, uint48 toSubId, uint256 amount + ); + event ApprovalForSub(address indexed owner, uint48 indexed subId, address indexed spender, uint256 amount); + + // Account management + function convertToSuper(address account) external returns (bool); + + function createSubAccount(address account) external returns (uint48); + + function accountType(address account) external view returns (AccountType); + + // Sub-account queries + function balanceOfSub(address superAccount, uint48 subId) external view returns (uint256); + + function subsCountOf(address superAccount) external view returns (uint48); + + function numberOfParameters() external pure returns (uint8); + + function parameterOf(uint8 paramIndex, address account) external view returns (uint64); + + function parameterOfSub(uint8 paramIndex, address account, uint48 subId) external view returns (uint64); + + function allowanceForSub(address owner, uint48 subId, address spender) external view returns (uint256); + + // Parametric transfers + function transferToSub(address toSuper, uint48 toSubId, uint256 amount) external returns (bool); + + function transferFromSub(uint48 fromSubId, address to, uint256 amount) external returns (bool); + + function transferBetweenSubs(uint48 fromSubId, uint48 toSubId, uint256 amount) external returns (bool); + + // Approved parametric transfers + function approveForSub(uint48 ownerSubId, address spender, uint256 amount) external returns (bool); + + function approvedTransferToSub(address from, address toSuper, uint48 toSubId, uint256 amount) + external + returns (bool); + + function approvedTransferFromSubToSub( + address fromSuper, + uint48 fromSubId, + address toSuper, + uint48 toSubId, + uint256 amount + ) external returns (bool); +} diff --git a/contracts/src/interfaces/ISignatureValidator.sol b/contracts/src/interfaces/ISignatureValidator.sol index c456937fc..2181aa6b1 100644 --- a/contracts/src/interfaces/ISignatureValidator.sol +++ b/contracts/src/interfaces/ISignatureValidator.sol @@ -22,6 +22,9 @@ ValidationResult constant VALIDATION_SUCCESS = ValidationResult.wrap(1); * construct the full message according to their specific signing scheme. */ interface ISignatureValidator { + error EmptyChannelId(); + error InvalidSignerAddress(); + /** * @notice Validates a participant's signature * @param channelId The channel identifier to be included in the signed message diff --git a/contracts/src/interfaces/IVault.sol b/contracts/src/interfaces/IVault.sol index f8fcaef24..a58884f38 100644 --- a/contracts/src/interfaces/IVault.sol +++ b/contracts/src/interfaces/IVault.sol @@ -13,7 +13,7 @@ interface IVault { * @param token Token address (use address(0) for native tokens) * @param amount Amount of tokens deposited */ - event Deposited(address indexed wallet, address indexed token, uint256 amount); + event Deposited(address indexed wallet, address indexed token, uint48 indexed subId, uint256 amount); /** * @notice Emitted when tokens are withdrawn from the contract @@ -21,7 +21,7 @@ interface IVault { * @param token Token address (use address(0) for native tokens) * @param amount Amount of tokens withdrawn */ - event Withdrawn(address indexed wallet, address indexed token, uint256 amount); + event Withdrawn(address indexed wallet, address indexed token, uint48 indexed subId, uint256 amount); /** * @notice Gets the balances of multiple accounts for multiple tokens @@ -30,7 +30,7 @@ interface IVault { * @param token Token address to check balance for (use address(0) for native tokens) * @return The balance of the specified token for the specified account */ - function getAccountBalance(address account, address token) external view returns (uint256); + function getAccountBalance(address account, address token, uint48 subId) external view returns (uint256); /** * @notice Deposits tokens into the contract @@ -39,7 +39,7 @@ interface IVault { * @param token Token address (use address(0) for native tokens) * @param amount Amount of tokens to deposit */ - function depositToVault(address account, address token, uint256 amount) external payable; + function depositToVault(address account, address token, uint48 subId, uint256 amount) external payable; /** * @notice Withdraws tokens from the contract @@ -48,5 +48,5 @@ interface IVault { * @param token Token address (use address(0) for native tokens) * @param amount Amount of tokens to withdraw */ - function withdrawFromVault(address account, address token, uint256 amount) external; + function withdrawFromVault(address account, address token, uint48 subId, uint256 amount) external; } diff --git a/contracts/src/interfaces/Types.sol b/contracts/src/interfaces/Types.sol index 364411e6d..efd49c6ea 100644 --- a/contracts/src/interfaces/Types.sol +++ b/contracts/src/interfaces/Types.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.30; +pragma solidity ^0.8.30; // ========= Channel Types ========== @@ -52,13 +52,11 @@ struct State { uint64 version; StateIntent intent; bytes32 metadata; - // to be added for fees logic: // bytes data; Ledger homeLedger; Ledger nonHomeLedger; - bytes userSig; bytes nodeSig; } @@ -67,10 +65,8 @@ struct Ledger { uint64 chainId; address token; uint8 decimals; - uint256 userAllocation; // FIXME: investigate whether naming the same thing differently in different components is good int256 userNetFlow; // can be negative as user can withdraw funds without depositing them (e.g., on a non-home chain) - uint256 nodeAllocation; int256 nodeNetFlow; // can be negative as node can withdraw user funds } diff --git a/contracts/src/sigValidators/ECDSAValidator.sol b/contracts/src/sigValidators/ECDSAValidator.sol index f17ab9c51..5bee7b456 100644 --- a/contracts/src/sigValidators/ECDSAValidator.sol +++ b/contracts/src/sigValidators/ECDSAValidator.sol @@ -35,6 +35,9 @@ contract ECDSAValidator is ISignatureValidator { bytes calldata signature, address participant ) external pure returns (ValidationResult) { + require(channelId != bytes32(0), EmptyChannelId()); + require(participant != address(0), InvalidSignerAddress()); + bytes memory message = Utils.pack(channelId, signingData); if (EcdsaSignatureUtils.validateEcdsaSigner(message, signature, participant)) { return VALIDATION_SUCCESS; diff --git a/contracts/src/sigValidators/SessionKeyValidator.sol b/contracts/src/sigValidators/SessionKeyValidator.sol index 34c8288a3..0302e3cba 100644 --- a/contracts/src/sigValidators/SessionKeyValidator.sol +++ b/contracts/src/sigValidators/SessionKeyValidator.sol @@ -71,6 +71,9 @@ contract SessionKeyValidator is ISignatureValidator { bytes calldata signature, address participant ) external pure returns (ValidationResult) { + require(channelId != bytes32(0), EmptyChannelId()); + require(participant != address(0), InvalidSignerAddress()); + (SessionKeyAuthorization memory skAuth, bytes memory skSignature) = abi.decode(signature, (SessionKeyAuthorization, bytes)); diff --git a/contracts/test/ChannelHub_Base.t.sol b/contracts/test/ChannelHub_Base.t.sol index 7ae2cfc4b..0227c1985 100644 --- a/contracts/test/ChannelHub_Base.t.sol +++ b/contracts/test/ChannelHub_Base.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.30; +pragma solidity ^0.8.30; import {Test} from "forge-std/Test.sol"; @@ -9,8 +9,9 @@ import {TestUtils, SESSION_KEY_VALIDATOR_ID} from "./TestUtils.sol"; import {ChannelHub} from "../src/ChannelHub.sol"; import {ECDSAValidator} from "../src/sigValidators/ECDSAValidator.sol"; import {SessionKeyValidator, SessionKeyAuthorization} from "../src/sigValidators/SessionKeyValidator.sol"; -import {State, StateIntent, Ledger} from "../src/interfaces/Types.sol"; +import {ChannelStatus, State, StateIntent, Ledger, DEFAULT_SIG_VALIDATOR_ID} from "../src/interfaces/Types.sol"; import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; +import {Utils} from "../src/Utils.sol"; // forge-lint: disable-next-item(unsafe-typecast) contract ChannelHubTest_Base is Test { @@ -22,6 +23,8 @@ contract ChannelHubTest_Base is Test { uint256 constant ALICE_SK1_PK = 3; uint256 constant BOB_PK = 4; + uint48 constant SUB_ID_0 = 0; + address node; address alice; address aliceSk1; @@ -53,13 +56,11 @@ contract ChannelHubTest_Base is Test { vm.startPrank(node); token.approve(address(cHub), INITIAL_BALANCE); - cHub.depositToVault(node, address(token), INITIAL_BALANCE); + cHub.depositToVault(node, address(token), SUB_ID_0, INITIAL_BALANCE); vm.stopPrank(); // Register SessionKeyValidator for the node - bytes memory skValidatorSig = TestUtils.buildAndSignValidatorRegistration( - vm, SESSION_KEY_VALIDATOR_ID, address(SK_SIG_VALIDATOR), NODE_PK - ); + bytes memory skValidatorSig = TestUtils.buildAndSignValidatorRegistration(vm, SESSION_KEY_VALIDATOR_ID, address(SK_SIG_VALIDATOR), NODE_PK); cHub.registerNodeValidator(node, SESSION_KEY_VALIDATOR_ID, SK_SIG_VALIDATOR, skValidatorSig); vm.prank(alice); @@ -69,36 +70,25 @@ contract ChannelHubTest_Base is Test { token.approve(address(cHub), INITIAL_BALANCE); } - function nextState(State memory state, StateIntent intent, uint256[2] memory allocations, int256[2] memory netFlows) - internal - pure - returns (State memory) - { - return State({ - version: state.version + 1, - intent: intent, - metadata: state.metadata, - homeLedger: Ledger({ - chainId: state.homeLedger.chainId, - token: state.homeLedger.token, - decimals: state.homeLedger.decimals, - userAllocation: allocations[0], - userNetFlow: netFlows[0], - nodeAllocation: allocations[1], - nodeNetFlow: netFlows[1] - }), - nonHomeLedger: Ledger({ - chainId: 0, - token: address(0), - decimals: 0, - userAllocation: 0, - userNetFlow: 0, - nodeAllocation: 0, - nodeNetFlow: 0 - }), - userSig: "", - nodeSig: "" - }); + function nextState(State memory state, StateIntent intent, uint256[2] memory allocations, int256[2] memory netFlows) internal pure returns (State memory) { + return + State({ + version: state.version + 1, + intent: intent, + metadata: state.metadata, + homeLedger: Ledger({ + chainId: state.homeLedger.chainId, + token: state.homeLedger.token, + decimals: state.homeLedger.decimals, + userAllocation: allocations[0], + userNetFlow: netFlows[0], + nodeAllocation: allocations[1], + nodeNetFlow: netFlows[1] + }), + nonHomeLedger: Ledger({chainId: 0, token: address(0), decimals: 0, userAllocation: 0, userNetFlow: 0, nodeAllocation: 0, nodeNetFlow: 0}), + userSig: "", + nodeSig: "" + }); } function nextState( @@ -111,31 +101,32 @@ contract ChannelHubTest_Base is Test { uint256[2] memory nonHomeAllocations, int256[2] memory nonHomeNetFlows ) internal pure returns (State memory) { - return State({ - version: state.version + 1, - intent: intent, - metadata: state.metadata, - homeLedger: Ledger({ - chainId: state.homeLedger.chainId, - token: state.homeLedger.token, - decimals: state.homeLedger.decimals, - userAllocation: allocations[0], - userNetFlow: netFlows[0], - nodeAllocation: allocations[1], - nodeNetFlow: netFlows[1] - }), - nonHomeLedger: Ledger({ - chainId: nonHomeChainId, - token: nonHomeChainToken, - decimals: 18, - userAllocation: nonHomeAllocations[0], - userNetFlow: nonHomeNetFlows[0], - nodeAllocation: nonHomeAllocations[1], - nodeNetFlow: nonHomeNetFlows[1] - }), - userSig: "", - nodeSig: "" - }); + return + State({ + version: state.version + 1, + intent: intent, + metadata: state.metadata, + homeLedger: Ledger({ + chainId: state.homeLedger.chainId, + token: state.homeLedger.token, + decimals: state.homeLedger.decimals, + userAllocation: allocations[0], + userNetFlow: netFlows[0], + nodeAllocation: allocations[1], + nodeNetFlow: netFlows[1] + }), + nonHomeLedger: Ledger({ + chainId: nonHomeChainId, + token: nonHomeChainToken, + decimals: 18, + userAllocation: nonHomeAllocations[0], + userNetFlow: nonHomeNetFlows[0], + nodeAllocation: nonHomeAllocations[1], + nodeNetFlow: nonHomeNetFlows[1] + }), + userSig: "", + nodeSig: "" + }); } function nextState( @@ -149,38 +140,35 @@ contract ChannelHubTest_Base is Test { uint256[2] memory nonHomeAllocations, int256[2] memory nonHomeNetFlows ) internal pure returns (State memory) { - return State({ - version: state.version + 1, - intent: intent, - metadata: state.metadata, - homeLedger: Ledger({ - chainId: state.homeLedger.chainId, - token: state.homeLedger.token, - decimals: state.homeLedger.decimals, - userAllocation: allocations[0], - userNetFlow: netFlows[0], - nodeAllocation: allocations[1], - nodeNetFlow: netFlows[1] - }), - nonHomeLedger: Ledger({ - chainId: nonHomeChainId, - token: nonHomeChainToken, - decimals: nonHomeDecimals, - userAllocation: nonHomeAllocations[0], - userNetFlow: nonHomeNetFlows[0], - nodeAllocation: nonHomeAllocations[1], - nodeNetFlow: nonHomeNetFlows[1] - }), - userSig: "", - nodeSig: "" - }); + return + State({ + version: state.version + 1, + intent: intent, + metadata: state.metadata, + homeLedger: Ledger({ + chainId: state.homeLedger.chainId, + token: state.homeLedger.token, + decimals: state.homeLedger.decimals, + userAllocation: allocations[0], + userNetFlow: netFlows[0], + nodeAllocation: allocations[1], + nodeNetFlow: netFlows[1] + }), + nonHomeLedger: Ledger({ + chainId: nonHomeChainId, + token: nonHomeChainToken, + decimals: nonHomeDecimals, + userAllocation: nonHomeAllocations[0], + userNetFlow: nonHomeNetFlows[0], + nodeAllocation: nonHomeAllocations[1], + nodeNetFlow: nonHomeNetFlows[1] + }), + userSig: "", + nodeSig: "" + }); } - function mutualSignStateBothWithEcdsaValidator(State memory state, bytes32 channelId, uint256 userPk) - internal - pure - returns (State memory) - { + function mutualSignStateBothWithEcdsaValidator(State memory state, bytes32 channelId, uint256 userPk) internal pure returns (State memory) { state.userSig = TestUtils.signStateEip191WithEcdsaValidator(vm, channelId, state, userPk); state.nodeSig = TestUtils.signStateEip191WithEcdsaValidator(vm, channelId, state, NODE_PK); return state; @@ -197,32 +185,36 @@ contract ChannelHubTest_Base is Test { return state; } - function verifyChannelState( + function signChallengeEip191WithEcdsaValidator(bytes32 channelId_, State memory state, uint256 privateKey) internal pure returns (bytes memory) { + bytes memory signingData = Utils.toSigningData(state); + bytes memory challengerSigningData = abi.encodePacked(signingData, "challenge"); + bytes memory message = Utils.pack(channelId_, challengerSigningData); + bytes memory signature = TestUtils.signEip191(vm, privateKey, message); + return abi.encodePacked(DEFAULT_SIG_VALIDATOR_ID, signature); + } + + function verifyChannelData( bytes32 channelId, - uint256 expectedUserAllocation, - int256 expectedUserNetFlow, - uint256 expectedNodeAllocation, - int256 expectedNodeNetFlow, + ChannelStatus expectedStatus, + uint64 expectedVersion, + uint256 expectedChallengeExpiry, string memory description ) internal view { - (,, State memory latestState,,) = cHub.getChannelData(channelId); - assertEq( - latestState.homeLedger.userAllocation, - expectedUserAllocation, - string.concat("User allocation ", description) - ); - assertEq(latestState.homeLedger.userNetFlow, expectedUserNetFlow, string.concat("User net flow ", description)); - assertEq( - latestState.homeLedger.nodeAllocation, - expectedNodeAllocation, - string.concat("Node allocation ", description) - ); - assertEq(latestState.homeLedger.nodeNetFlow, expectedNodeNetFlow, string.concat("Node net flow ", description)); - - uint256 nodeBalance = cHub.getAccountBalance(node, address(token)); - uint256 expectedNodeBalance = expectedNodeNetFlow < 0 - ? INITIAL_BALANCE + uint256(-expectedNodeNetFlow) - : INITIAL_BALANCE - uint256(expectedNodeNetFlow); - assertEq(nodeBalance, expectedNodeBalance, string.concat("Node vault balance ", description)); + (ChannelStatus status, , State memory latestState, uint256 challengeExpiry, ) = cHub.getChannelData(channelId); + assertEq(uint8(status), uint8(expectedStatus), string.concat(description, ": Channel status: ")); + assertEq(latestState.version, expectedVersion, string.concat(description, ": Channel version: ")); + assertEq(challengeExpiry, expectedChallengeExpiry, string.concat(description, ": Challenge expiry: ")); + } + + function verifyChannelState(bytes32 channelId, uint256[2] memory allocations, int256[2] memory netFlows, string memory description) internal view { + (, , State memory latestState, , ) = cHub.getChannelData(channelId); + assertEq(latestState.homeLedger.userAllocation, allocations[0], string.concat(description, ": User allocation: ")); + assertEq(latestState.homeLedger.userNetFlow, netFlows[0], string.concat(description, ": User net flow: ")); + assertEq(latestState.homeLedger.nodeAllocation, allocations[1], string.concat(description, ": Node allocation: ")); + assertEq(latestState.homeLedger.nodeNetFlow, netFlows[1], string.concat(description, ": Node net flow: ")); + + uint256 nodeBalance = cHub.getAccountBalance(node, address(token), SUB_ID_0); + uint256 expectedNodeBalance = netFlows[1] < 0 ? INITIAL_BALANCE + uint256(-netFlows[1]) : INITIAL_BALANCE - uint256(netFlows[1]); + assertEq(nodeBalance, expectedNodeBalance, string.concat(description, ": Node balance: ")); } } diff --git a/contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol new file mode 100644 index 000000000..037a9d3ca --- /dev/null +++ b/contracts/test/ChannelHub_challenge/ChannelHub_Challenge_Base.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; + +import {Utils} from "../../src/Utils.sol"; +import {State, ChannelDefinition, StateIntent, Ledger} from "../../src/interfaces/Types.sol"; + +/** + * @dev Base contract for challenge tests with common helper functions. + */ +abstract contract ChannelHubTest_Challenge_Base is ChannelHubTest_Base { + ChannelDefinition internal def; + bytes32 internal channelId; + State internal initState; + + uint64 constant NON_HOME_CHAIN_ID = 42; + address constant NON_HOME_TOKEN = address(0x42); + + function setUp() public virtual override { + super.setUp(); + + def = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + + channelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + } + + function createChannelWithDeposit() internal { + initState = State({ + version: 0, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 1000, + userNetFlow: 1000, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: 0, + token: address(0), + decimals: 0, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.createChannel(def, initState); + } +} diff --git a/contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol new file mode 100644 index 000000000..7e999f05d --- /dev/null +++ b/contracts/test/ChannelHub_challenge/ChannelHub_challengeHomeChain.t.sol @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import { ChannelHubTest_Challenge_Base } from "./ChannelHub_Challenge_Base.t.sol"; + +// forge-lint: disable-start(unsafe-typecast) + +import { Utils } from "../../src/Utils.sol"; +import { State, ChannelDefinition, StateIntent, Ledger, ChannelStatus, ParticipantIndex } from "../../src/interfaces/Types.sol"; +import { ChannelHub } from "../../src/ChannelHub.sol"; +import { ChannelEngine } from "../../src/ChannelEngine.sol"; + +/* + * @dev This file uses integration / blackbox testing through ChannelHub to verify + * critical end-to-end challenge flows (signature validation, fund movements, storage updates, events). + * Complex state machine logic and edge cases are tested exhaustively in dedicated engine unit tests + * (ChannelEngine.t.sol, EscrowDepositEngine.t.sol, EscrowWithdrawalEngine.t.sol) for faster execution + * and better isolation. + */ +contract ChannelHubTest_Challenge_HomeChain_NormalOperation is ChannelHubTest_Challenge_Base { + /* + Test cases: + - a channel can be challenged with a newer state, which is enforced during challenge + - a channel can be challenged with existing state, which is NOT enforced the second time during challenge + - challenge is finalized (funds can be withdrawn) after `challengeExpireAt` time expires + - challenged "operating" state can be resolved with a newer state until `challengeExpireAt` time has NOT passed + - challenged state can NOT be resolved after `challengeExpireAt` time has passed + - a channel can NOT be challenged again during a challenge + - a channel can NOT be challenged with an earlier state + - a non-yet-on-chain channel can NOT be challenged + */ + + function setUp() public override { + super.setUp(); + createChannelWithDeposit(); + } + + function test_challengeWithNewerState_enforcesState() public { + // Off-chain: user transfers 100 to node + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // Off-chain: user transfers another 50 to node + State memory stateV2 = nextState(stateV1, StateIntent.OPERATE, [uint256(850), uint256(0)], [int256(1000), int256(-150)]); + stateV2 = mutualSignStateBothWithEcdsaValidator(stateV2, channelId, ALICE_PK); + + // Node challenges with newer state V2, which should be enforced during challenge + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV2, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV2, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, 2, block.timestamp + CHALLENGE_DURATION, "State V2 should be enforced during challenge"); + verifyChannelState(channelId, [uint256(850), uint256(0)], [int256(1000), int256(-150)], "State V2 should be enforced during challenge"); + } + + function test_challengeWithExistingState_notEnforcedAgain() public { + // Checkpoint a new state + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + vm.prank(alice); + cHub.checkpointChannel(channelId, stateV1); + + // Verify state V1 is on-chain + (, , State memory latestStateBefore, , ) = cHub.getChannelData(channelId); + assertEq(latestStateBefore.version, 1, "State version should be 1 before challenge"); + + // Node challenges with the same state V1 (already on-chain) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV1, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, 1, block.timestamp + CHALLENGE_DURATION, "State V1 should be enforced during challenge"); + verifyChannelState(channelId, [uint256(900), uint256(0)], [int256(1000), int256(-100)], "State V1 should be enforced during challenge"); + } + + function test_challengeFinalization_afterTimeout() public { + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // Challenge with current state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV1, challengerSig, ParticipantIndex.NODE); + + vm.warp(block.timestamp + CHALLENGE_DURATION + 1); + + uint256 aliceBalanceBefore = token.balanceOf(alice); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); + + // Finalize challenge by closing the channel (unilateral closure) + // When doing unilateral closure after timeout, any state works + vm.prank(alice); + cHub.closeChannel(channelId, initState); + + // Verify channel is CLOSED and funds were distributed according to last enforced state (V1) + verifyChannelData(channelId, ChannelStatus.CLOSED, 1, 0, "Channel should be CLOSED after challenge finalization"); + + uint256 aliceBalanceAfter = token.balanceOf(alice); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token), SUB_ID_0); + + assertEq(aliceBalanceAfter, aliceBalanceBefore + 900, "Alice should receive her allocation"); + // Node balance should remain unchanged because: + // 1. The node already received its 100 when the challenge was processed (nodeNetFlow -100 released funds) + // 2. During unilateral closure, node gets nodeAllocation (0) + assertEq(nodeBalanceAfter, nodeBalanceBefore, "Node balance should remain unchanged (already received net flow during challenge)"); + } + + function test_resolveChallenge_withNewerState_beforeTimeout() public { + // State V1: user transfers 100 + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // Challenge with stateV1 + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV1, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, 1, block.timestamp + CHALLENGE_DURATION, "Channel should be DISPUTED after challenge"); + + // State V2: user transfers another 50 (newer state to resolve challenge) + State memory stateV2 = nextState(stateV1, StateIntent.OPERATE, [uint256(850), uint256(0)], [int256(1000), int256(-150)]); + stateV2 = mutualSignStateBothWithEcdsaValidator(stateV2, channelId, ALICE_PK); + + // Resolve challenge by checkpointing newer state (before timeout) + vm.prank(alice); + cHub.checkpointChannel(channelId, stateV2); + + verifyChannelData(channelId, ChannelStatus.OPERATING, 2, 0, "Channel should be OPERATING after resolving challenge with newer state"); + verifyChannelState( + channelId, + [uint256(850), uint256(0)], + [int256(1000), int256(-150)], + "State V2 should be enforced after resolving challenge with newer state" + ); + } + + function test_revert_resolveChallenge_withOlderState_beforeTimeout() public { + // State V1: user transfers 100 + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // State V2: user receives 50 back + State memory stateV2 = nextState(stateV1, StateIntent.OPERATE, [uint256(950), uint256(0)], [int256(1000), int256(-50)]); + stateV2 = mutualSignStateBothWithEcdsaValidator(stateV2, channelId, ALICE_PK); + + // Challenge with stateV2 + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV2, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV2, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, 2, block.timestamp + CHALLENGE_DURATION, "Channel should be DISPUTED after challenge"); + + // Try to resolve with older state V1 (should fail) + vm.expectRevert(ChannelEngine.IncorrectStateVersion.selector); + vm.prank(alice); + cHub.checkpointChannel(channelId, stateV1); + } + + function test_revert_resolveChallenge_withNewerState_afterTimeout() public { + // State V1 + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // Challenge + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, stateV1, challengerSig, ParticipantIndex.NODE); + + vm.warp(block.timestamp + CHALLENGE_DURATION + 1); + + // State V2: user transfers another 50 (newer state to resolve challenge) + State memory stateV2 = nextState(stateV1, StateIntent.OPERATE, [uint256(850), uint256(0)], [int256(1000), int256(-150)]); + stateV2 = mutualSignStateBothWithEcdsaValidator(stateV2, channelId, ALICE_PK); + + // Cannot resolve challenge after timeout - must close channel instead + vm.expectRevert(ChannelEngine.ChallengeExpired.selector); + vm.prank(alice); + cHub.checkpointChannel(channelId, stateV2); + } + + function test_revert_challengeAlreadyChallengedChannel() public { + // First challenge + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED + verifyChannelData(channelId, ChannelStatus.DISPUTED, 0, block.timestamp + CHALLENGE_DURATION, "Channel should be DISPUTED after first challenge"); + + // Try to challenge again (should fail) + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(850), uint256(0)], [int256(1000), int256(-150)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + bytes memory challengerSig2 = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.challengeChannel(channelId, stateV1, challengerSig2, ParticipantIndex.NODE); + } + + function test_revert_challengeWithOlderState() public { + // State V1 + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + // Checkpoint V1 + vm.prank(alice); + cHub.checkpointChannel(channelId, stateV1); + + // Try to challenge with older state (initial) (should fail) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.ChallengerVersionTooLow.selector); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + } + + function test_revert_challengeNonExistingChannel() public { + ChannelDefinition memory newDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE + 42, + approvedSignatureValidators: 0, + metadata: bytes32("42") + }); + bytes32 newChannelId = Utils.getChannelId(newDef, CHANNEL_HUB_VERSION); + + // Off-chain: user transfers 100 to node + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [uint256(900), uint256(0)], [int256(1000), int256(-100)]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, newChannelId, ALICE_PK); + + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(newChannelId, stateV1, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.challengeChannel(newChannelId, stateV1, challengerSig, ParticipantIndex.NODE); + } +} + +contract ChannelHubTest_Challenge_HomeChain_EscrowDeposit is ChannelHubTest_Challenge_Base { + /* + Test cases: + - a channel can be challenged with a newer state, which is enforced during challenge: + (new: InitiateEscrowDeposit, FinalizeEscrowDeposit) + - a channel can be challenged with existing state, which is NOT enforced the second time during challenge: + (existing: InitiateEscrowDeposit, FinalizeEscrowDeposit) + - a challenged channel can be resolved with "InitiateEscrowDeposit" / "FinalizeEscrowDeposit" state until `challengeExpireAt` time has NOT passed + */ + + bytes32 escrowId; + + uint64 initiateEscrowDepositVersion = 1; + State initiateEscrowDepositState; + uint64 finalizeEscrowDepositVersion = 2; + State finalizeEscrowDepositState; + + function setUp() public override { + super.setUp(); + createChannelWithDeposit(); + + initiateEscrowDepositState = nextState( + initState, + StateIntent.INITIATE_ESCROW_DEPOSIT, + [uint256(1000), uint256(500)], + [int256(1000), int256(500)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(500), uint256(0)], + [int256(500), int256(0)] + ); + initiateEscrowDepositState = mutualSignStateBothWithEcdsaValidator(initiateEscrowDepositState, channelId, ALICE_PK); + + escrowId = Utils.getEscrowId(channelId, initiateEscrowDepositVersion); + + finalizeEscrowDepositState = nextState( + initiateEscrowDepositState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [uint256(1500), uint256(0)], + [int256(1000), int256(500)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(0), uint256(0)], + [int256(500), int256(-500)] + ); + finalizeEscrowDepositState = mutualSignStateBothWithEcdsaValidator(finalizeEscrowDepositState, channelId, ALICE_PK); + } + + function test_challenge_initiateEscrowDeposit_asNew() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and initiateEscrowDepositState was enforced + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + initiateEscrowDepositVersion, + block.timestamp + CHALLENGE_DURATION, + "InitiateEscrowDepositState should be enforced" + ); + verifyChannelState(channelId, [uint256(1000), uint256(500)], [int256(1000), int256(500)], "InitiateEscrowDepositState should be enforced"); + } + + function test_challenge_initiateEscrowDeposit_asExisting() public { + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Challenge with already enforced initiateEscrowDepositState state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Verify state is still initiateEscrowDepositState + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + initiateEscrowDepositVersion, + block.timestamp + CHALLENGE_DURATION, + "State should not be re-enforced" + ); + verifyChannelState(channelId, [uint256(1000), uint256(500)], [int256(1000), int256(500)], "State should not be re-enforced"); + } + + function test_challenge_initiateEscrowDeposit_resolve() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with newer initiateEscrowDepositState state (before timeout) + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, initiateEscrowDepositVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(1000), uint256(500)], [int256(1000), int256(500)], "initiateEscrowDepositState should be enforced"); + } + + function test_challenge_finalizeEscrowDeposit_asNew() public { + // First enforce INITIATE_ESCROW_DEPOSIT on-chain (required for FINALIZE to be valid) + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Now challenge with FINALIZE_ESCROW_DEPOSIT + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, finalizeEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and finalizeEscrowDepositState was enforced + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + finalizeEscrowDepositVersion, + block.timestamp + CHALLENGE_DURATION, + "FinalizeEscrowDepositState should be enforced" + ); + verifyChannelState(channelId, [uint256(1500), uint256(0)], [int256(1000), int256(500)], "finalizeEscrowDepositState should be enforced"); + } + + function test_challenge_finalizeEscrowDeposit_asExisting() public { + // First enforce INITIATE_ESCROW_DEPOSIT on-chain + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Then enforce FINALIZE_ESCROW_DEPOSIT on-chain + vm.prank(alice); + cHub.finalizeEscrowDeposit(channelId, escrowId, finalizeEscrowDepositState); + + // Challenge with already enforced finalizeEscrowDepositState state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, finalizeEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Verify state is still finalizeEscrowDepositState + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + finalizeEscrowDepositVersion, + block.timestamp + CHALLENGE_DURATION, + "State should not be re-enforced" + ); + verifyChannelState(channelId, [uint256(1500), uint256(0)], [int256(1000), int256(500)], "State should not be re-enforced"); + } + + function test_challenge_finalizeEscrowDeposit_resolve() public { + // First enforce INITIATE_ESCROW_DEPOSIT on-chain + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Challenge with older initiate state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with newer finalizeEscrowDepositState state (before timeout) + vm.prank(alice); + cHub.finalizeEscrowDeposit(channelId, escrowId, finalizeEscrowDepositState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, finalizeEscrowDepositVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(1500), uint256(0)], [int256(1000), int256(500)], "finalizeEscrowDepositState should be enforced"); + } + + function test_finalizeEscrowDeposit_resolve_newlyChallenged_initializeEscrowDeposit() public { + // Challenge with INITIATE_ESCROW_DEPOSIT state (without enforcing it on-chain first) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowDepositState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with finalizeEscrowDepositState state (before timeout) + vm.prank(alice); + cHub.finalizeEscrowDeposit(channelId, escrowId, finalizeEscrowDepositState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, finalizeEscrowDepositVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(1500), uint256(0)], [int256(1000), int256(500)], "finalizeEscrowDepositState should be enforced"); + } + + function test_revert_onChallengeEscrowDeposit() public { + // First enforce INITIATE_ESCROW_DEPOSIT on-chain + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + // Challenge with INITIATE_ESCROW_DEPOSIT state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.NODE); + } +} + +contract ChannelHubTest_Challenge_HomeChain_EscrowWithdrawal is ChannelHubTest_Challenge_Base { + /* + Test cases: + - a channel can be challenged with a newer state, which is enforced during challenge: + (new: InitiateEscrowWithdrawal, FinalizeEscrowWithdrawal) + - a channel can be challenged with existing state, which is NOT enforced the second time during challenge: + (existing: InitiateEscrowWithdrawal, FinalizeEscrowWithdrawal) + - a challenged channel can be resolved with "InitiateEscrowWithdrawal" / "FinalizeEscrowWithdrawal" state until `challengeExpireAt` time has NOT passed + */ + + bytes32 escrowId; + + uint64 initiateEscrowWithdrawalVersion = 1; + State initiateEscrowWithdrawalState; + uint64 finalizeEscrowWithdrawalVersion = 2; + State finalizeEscrowWithdrawalState; + + function setUp() public override { + super.setUp(); + createChannelWithDeposit(); + + initiateEscrowWithdrawalState = nextState( + initState, + StateIntent.INITIATE_ESCROW_WITHDRAWAL, + [uint256(1000), uint256(0)], + [int256(1000), int256(0)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(0), uint256(300)], + [int256(0), int256(300)] + ); + initiateEscrowWithdrawalState = mutualSignStateBothWithEcdsaValidator(initiateEscrowWithdrawalState, channelId, ALICE_PK); + + escrowId = Utils.getEscrowId(channelId, initiateEscrowWithdrawalVersion); + + finalizeEscrowWithdrawalState = nextState( + initiateEscrowWithdrawalState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(700), uint256(0)], + [int256(1000), int256(-300)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(0), uint256(0)], + [int256(-300), int256(300)] + ); + finalizeEscrowWithdrawalState = mutualSignStateBothWithEcdsaValidator(finalizeEscrowWithdrawalState, channelId, ALICE_PK); + } + + function test_challenge_initiateEscrowWithdrawal_asNew() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowWithdrawalState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and initiateEscrowWithdrawalState was enforced + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + initiateEscrowWithdrawalVersion, + block.timestamp + CHALLENGE_DURATION, + "InitiateEscrowWithdrawalState should be enforced" + ); + verifyChannelState(channelId, [uint256(1000), uint256(0)], [int256(1000), int256(0)], "InitiateEscrowWithdrawalState should be enforced"); + } + + function test_challenge_initiateEscrowWithdrawal_asExisting() public { + vm.prank(alice); + cHub.initiateEscrowWithdrawal(def, initiateEscrowWithdrawalState); + + // Challenge with already enforced initiateEscrowWithdrawalState state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowWithdrawalState, challengerSig, ParticipantIndex.NODE); + + // Verify state is still initiateEscrowWithdrawalState + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + initiateEscrowWithdrawalVersion, + block.timestamp + CHALLENGE_DURATION, + "State should not be re-enforced" + ); + verifyChannelState(channelId, [uint256(1000), uint256(0)], [int256(1000), int256(0)], "State should not be re-enforced"); + } + + function test_challenge_initiateEscrowWithdrawal_resolve() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with newer initiateEscrowWithdrawalState state (before timeout) + vm.prank(alice); + cHub.initiateEscrowWithdrawal(def, initiateEscrowWithdrawalState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, initiateEscrowWithdrawalVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(1000), uint256(0)], [int256(1000), int256(0)], "initiateEscrowWithdrawalState should be enforced"); + } + + function test_challenge_finalizeEscrowWithdrawal_asNew() public { + // INITIATE_ESCROW_WITHDRAWAL is NOT required to be enforced first on-chain + + // Challenge with FINALIZE_ESCROW_WITHDRAWAL + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, finalizeEscrowWithdrawalState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and finalizeEscrowWithdrawalState was enforced + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + finalizeEscrowWithdrawalVersion, + block.timestamp + CHALLENGE_DURATION, + "FinalizeEscrowWithdrawalState should be enforced" + ); + verifyChannelState(channelId, [uint256(700), uint256(0)], [int256(1000), int256(-300)], "finalizeEscrowWithdrawalState should be enforced"); + } + + function test_challenge_finalizeEscrowWithdrawal_asExisting() public { + // INITIATE_ESCROW_WITHDRAWAL is NOT required to be enforced first on-chain + + // Enforce FINALIZE_ESCROW_WITHDRAWAL on-chain + vm.prank(alice); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, finalizeEscrowWithdrawalState); + + // Challenge with already enforced finalizeEscrowWithdrawalState state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, finalizeEscrowWithdrawalState, challengerSig, ParticipantIndex.NODE); + + // Verify state is still finalizeEscrowWithdrawalState + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + finalizeEscrowWithdrawalVersion, + block.timestamp + CHALLENGE_DURATION, + "State should not be re-enforced" + ); + verifyChannelState(channelId, [uint256(700), uint256(0)], [int256(1000), int256(-300)], "State should not be re-enforced"); + } + + function test_challenge_finalizeEscrowWithdrawal_resolve() public { + // INITIATE_ESCROW_WITHDRAWAL is NOT required to be enforced first on-chain + + // Challenge with older initiate state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with newer finalizeEscrowWithdrawalState state (before timeout) + vm.prank(alice); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, finalizeEscrowWithdrawalState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, finalizeEscrowWithdrawalVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(700), uint256(0)], [int256(1000), int256(-300)], "finalizeEscrowWithdrawalState should be enforced"); + } + + function test_finalizeEscrowWithdrawal_resolve_newlyChallenged_initializeEscrowWithdrawal() public { + // Challenge with INITIATE_ESCROW_WITHDRAWAL state (without enforcing it on-chain first) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateEscrowWithdrawalState, challengerSig, ParticipantIndex.NODE); + + // Resolve challenge with finalizeEscrowWithdrawalState state (before timeout) + vm.prank(alice); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, finalizeEscrowWithdrawalState); + + // Verify challenge was resolved + verifyChannelData(channelId, ChannelStatus.OPERATING, finalizeEscrowWithdrawalVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(700), uint256(0)], [int256(1000), int256(-300)], "finalizeEscrowWithdrawalState should be enforced"); + } + + function test_revert_onChallengeEscrowWithdrawal() public { + // First enforce INITIATE_ESCROW_WITHDRAWAL on-chain + vm.prank(alice); + cHub.initiateEscrowWithdrawal(def, initiateEscrowWithdrawalState); + + // Challenge with INITIATE_ESCROW_WITHDRAWAL state + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.NODE); + } +} + +contract ChannelHubTest_Challenge_HomeChain_HomeMigration is ChannelHubTest_Challenge_Base { + /* + Test cases: + - a channel in Operate status can be challenged with initiated migration state + - a channel challenged with "InitiateMigration" state can be checkpointed calling "finalizeMigration" (-> MigratedOut status) + - a channel challenged with "InitiateMigration" state can be resolved with "operation" state + (although this should not happen in practice since the node should finalize migration instead of resolving with an older state, but just to be safe) + - a channel can NOT be challenged when in MIGRATED_OUT status + - a channel can NOT be challenged in Operating status with finalize migration state (use `finalizeMigration` function instead) + */ + + uint64 initiateMigrationVersion = 1; + State initiateMigrationState; + uint64 finalizeMigrationVersion = 2; + State finalizeMigrationState; + uint64 operateAfterMigrationInitVersion = 2; + State operateAfterMigrationInitState; + + // New channel for testing NEW home chain behavior + ChannelDefinition newHomeDef; + bytes32 newHomeChannelId; + State newHomeInitiateMigrationState; + uint64 newHomeOperateVersion = 3; + State newHomeOperateState; + + function setUp() public override { + super.setUp(); + createChannelWithDeposit(); + + // INITIATE_MIGRATION state: + initiateMigrationState = nextState( + initState, + StateIntent.INITIATE_MIGRATION, + [uint256(700), uint256(0)], + [int256(1000), int256(-300)], + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(0), uint256(700)], // Node locks user allocation on new home + [int256(0), int256(700)] + ); + initiateMigrationState = mutualSignStateBothWithEcdsaValidator(initiateMigrationState, channelId, ALICE_PK); + + // FINALIZE_MIGRATION state: Allocations zero out on old home, user receives allocation on new home + finalizeMigrationState = nextState( + initiateMigrationState, + StateIntent.FINALIZE_MIGRATION, + [uint256(0), uint256(0)], // Old home: allocations zero out + [int256(1000), int256(-1000)], // Old home: net flows balance + NON_HOME_CHAIN_ID, + NON_HOME_TOKEN, + [uint256(700), uint256(0)], // New home: user receives allocation + [int256(0), int256(700)] + ); + // Swap home and non-home states as per migration protocol + Ledger memory temp = finalizeMigrationState.homeLedger; + finalizeMigrationState.homeLedger = finalizeMigrationState.nonHomeLedger; + finalizeMigrationState.nonHomeLedger = temp; + finalizeMigrationState = mutualSignStateBothWithEcdsaValidator(finalizeMigrationState, channelId, ALICE_PK); + + // OPERATE state after migration initiation (for resolving challenge) + operateAfterMigrationInitState = nextState(initiateMigrationState, StateIntent.OPERATE, [uint256(650), uint256(0)], [int256(1000), int256(-350)]); + operateAfterMigrationInitState = mutualSignStateBothWithEcdsaValidator(operateAfterMigrationInitState, channelId, ALICE_PK); + } + + function test_challenge_initiateMigration_fromOperating() public { + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateMigrationState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and initiateMigrationState was enforced + verifyChannelData( + channelId, + ChannelStatus.DISPUTED, + initiateMigrationVersion, + block.timestamp + CHALLENGE_DURATION, + "InitiateMigrationState should be enforced" + ); + verifyChannelState(channelId, [uint256(700), uint256(0)], [int256(1000), int256(-300)], "InitiateMigrationState should be enforced"); + } + + function test_challenge_initiateMigration_resolve_withFinalizeMigration() public { + // Challenge with INITIATE_MIGRATION + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateMigrationState, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, initiateMigrationVersion, block.timestamp + CHALLENGE_DURATION, "Channel should be DISPUTED"); + + // Resolve challenge with FINALIZE_MIGRATION (before timeout) + vm.prank(alice); + cHub.finalizeMigration(channelId, finalizeMigrationState); + + // Verify channel is MIGRATED_OUT and initiateMigrationState was enforced + verifyChannelData(channelId, ChannelStatus.MIGRATED_OUT, finalizeMigrationVersion, 0, "finalizeMigration should resolve the challenge"); + } + + function test_challenge_initiateMigration_resolve_withOperate() public { + // Challenge with INITIATE_MIGRATION + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, initiateMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(channelId, initiateMigrationState, challengerSig, ParticipantIndex.NODE); + + verifyChannelData(channelId, ChannelStatus.DISPUTED, initiateMigrationVersion, block.timestamp + CHALLENGE_DURATION, "Channel should be DISPUTED"); + + // Resolve challenge with newer OPERATE state (before timeout) + // This is technically possible but shouldn't happen in practice as participants should NOT sign OPERATE state as direct successor of INITIATE_MIGRATION + vm.prank(alice); + cHub.checkpointChannel(channelId, operateAfterMigrationInitState); + + // Verify channel is back to OPERATING + verifyChannelData(channelId, ChannelStatus.OPERATING, operateAfterMigrationInitVersion, 0, "Challenge should be resolved"); + verifyChannelState(channelId, [uint256(650), uint256(0)], [int256(1000), int256(-350)], "operateAfterMigrationInitState should be enforced"); + } + + function test_revert_challenge_migratedOut() public { + // First initiate migration + vm.prank(alice); + cHub.initiateMigration(def, initiateMigrationState); + + // Then finalize migration to put channel in MIGRATED_OUT status + vm.prank(alice); + cHub.finalizeMigration(channelId, finalizeMigrationState); + + // Verify channel is in MIGRATED_OUT status + verifyChannelData(channelId, ChannelStatus.MIGRATED_OUT, finalizeMigrationVersion, 0, "Channel should be MIGRATED_OUT"); + + // Try to challenge channel in MIGRATED_OUT status (should fail) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeMigrationState, NODE_PK); + + vm.prank(node); + vm.expectRevert(ChannelHub.IncorrectChannelStatus.selector); + cHub.challengeChannel(channelId, finalizeMigrationState, challengerSig, ParticipantIndex.NODE); + } + + function test_revert_challenge_operating_withFinalizeMigration() public { + // Channel is in OPERATING status + // Try to challenge with FINALIZE_MIGRATION without INITIATE_MIGRATION first (should fail) + bytes memory challengerSig = signChallengeEip191WithEcdsaValidator(channelId, finalizeMigrationState, NODE_PK); + + vm.prank(node); + // NOTE: IncorrectHomeChainId check happens before IncorrectPreviousStateIntent check + // finalizeMigrationState has swapped ledgers, so homeLedger.chainId != block.chainid + vm.expectRevert(ChannelEngine.IncorrectHomeChainId.selector); + cHub.challengeChannel(channelId, finalizeMigrationState, challengerSig, ParticipantIndex.NODE); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_challenge/ChannelHub_challengeNonHomeChain.t.sol b/contracts/test/ChannelHub_challenge/ChannelHub_challengeNonHomeChain.t.sol new file mode 100644 index 000000000..1c1a15e96 --- /dev/null +++ b/contracts/test/ChannelHub_challenge/ChannelHub_challengeNonHomeChain.t.sol @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +import {ChannelHubTest_Challenge_Base} from "./ChannelHub_Challenge_Base.t.sol"; + +// forge-lint: disable-start(unsafe-typecast) + +import {Utils} from "../../src/Utils.sol"; +import { + ChannelDefinition, + ChannelStatus, + State, + StateIntent, + Ledger, + EscrowStatus, + ParticipantIndex +} from "../../src/interfaces/Types.sol"; +import {ChannelHub} from "../../src/ChannelHub.sol"; +import {EscrowDepositEngine} from "../../src/EscrowDepositEngine.sol"; +import {EscrowWithdrawalEngine} from "../../src/EscrowWithdrawalEngine.sol"; + +/* + * @dev This file uses integration / blackbox testing through ChannelHub to verify + * critical end-to-end challenge flows (signature validation, fund movements, storage updates, events). + * Complex state machine logic and edge cases are tested exhaustively in dedicated engine unit tests + * (ChannelEngine.t.sol, EscrowDepositEngine.t.sol, EscrowWithdrawalEngine.t.sol) for faster execution + * and better isolation. + */ + +contract ChannelHubTest_Challenge_NonHomeChain_EscrowDeposit is ChannelHubTest_Challenge_Base { + /* + - reverts on challenging NON-EXISTENT escrow deposit + - escrow deposit can be challenged until `unlockAt` time has NOT passed + - escrow deposit can NOT be challenged after `unlockAt` time has passed + - challenged escrow deposit can be resolved until `challengeExpireAt` time has passed with a newer finalization state, which removes challenge and unlock funds + - challenged escrow deposit can NOT be resolved if `challengeExpireAt` has passed, but + can be withdrawn after `challengeExpireAt` time passes + - reverts on challenging already challenged escrow deposit + */ + + uint64 constant ESCROW_VERSION = 1; + uint256 constant ESCROW_AMOUNT = 500; + + bytes32 escrowId; + State initiateEscrowDepositState; + State finalizeEscrowDepositState; + + function setUp() public override { + super.setUp(); + // `def` and `channelId` are set by ChannelHubTest_Challenge_Base.setUp() + // For non-home chain: NON_HOME_CHAIN_ID (42) is the home chain, block.chainid is non-home + + initiateEscrowDepositState = State({ + version: ESCROW_VERSION, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, // 42 — this IS the home chain (not current chain) + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: 500, + userNetFlow: 500, + nodeAllocation: ESCROW_AMOUNT, // must equal deposit amount in WAD (same decimals here) + nodeNetFlow: int256(ESCROW_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), // current chain is non-home + token: address(token), + decimals: 18, + userAllocation: ESCROW_AMOUNT, + userNetFlow: int256(ESCROW_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + initiateEscrowDepositState = + mutualSignStateBothWithEcdsaValidator(initiateEscrowDepositState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.initiateEscrowDeposit(def, initiateEscrowDepositState); + + escrowId = Utils.getEscrowId(channelId, ESCROW_VERSION); + + // Finalize state (version = ESCROW_VERSION + 1): + // home: userAllocation += ESCROW_AMOUNT, nodeAllocation = 0, userNetFlow unchanged + // non-home: allocations = 0; userNetFlow = +ESCROW_AMOUNT, nodeNetFlow = -ESCROW_AMOUNT + finalizeEscrowDepositState = nextState( + initiateEscrowDepositState, + StateIntent.FINALIZE_ESCROW_DEPOSIT, + [uint256(500 + ESCROW_AMOUNT), uint256(0)], + [int256(500), int256(ESCROW_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [int256(ESCROW_AMOUNT), -int256(ESCROW_AMOUNT)] + ); + finalizeEscrowDepositState = + mutualSignStateBothWithEcdsaValidator(finalizeEscrowDepositState, channelId, ALICE_PK); + } + + function _challengeEscrowDeposit() internal { + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + vm.prank(node); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.NODE); + } + + function test_revert_challengeEscrowDeposit_nonExistentEscrow() public { + bytes32 nonExistentEscrowId = Utils.getEscrowId(channelId, 999); + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + vm.prank(node); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); + cHub.challengeEscrowDeposit(nonExistentEscrowId, challengerSig, ParticipantIndex.NODE); + } + + function test_success_challengeEscrowDeposit_beforeUnlockAt() public { + _challengeEscrowDeposit(); + + (, EscrowStatus status,, uint64 challengeExpireAt,,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(status), uint8(EscrowStatus.DISPUTED), "Escrow should be DISPUTED after challenge"); + assertEq( + challengeExpireAt, + uint64(block.timestamp) + EscrowDepositEngine.CHALLENGE_DURATION, + "challengeExpireAt should be set to timestamp + CHALLENGE_DURATION" + ); + } + + function test_revert_challengeEscrowDeposit_afterUnlockAt() public { + vm.warp(block.timestamp + cHub.ESCROW_DEPOSIT_UNLOCK_DELAY() + 1); + + vm.expectRevert(EscrowDepositEngine.UnlockPeriodPassed.selector); + _challengeEscrowDeposit(); + } + + function test_resolveChallengedEscrowDeposit_withFinalizeState_beforeChallengeExpiry() public { + _challengeEscrowDeposit(); + + (, EscrowStatus statusAfterChallenge,,,,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(statusAfterChallenge), uint8(EscrowStatus.DISPUTED), "Should be DISPUTED after challenge"); + + uint256 nodeVaultBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); + + // Cooperative finalization with FINALIZE state (before challengeExpireAt) + vm.prank(node); + cHub.finalizeEscrowDeposit(channelId, escrowId, finalizeEscrowDepositState); + + (, EscrowStatus statusAfterFinalize,,, uint256 lockedAmount,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(statusAfterFinalize), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(lockedAmount, 0, "Locked amount should be 0 after finalization"); + + // Cooperative path: locked funds released to node vault (node earned them for providing cross-chain liquidity) + assertEq( + cHub.getAccountBalance(node, address(token), SUB_ID_0), + nodeVaultBefore + ESCROW_AMOUNT, + "Node vault should receive locked amount" + ); + } + + function test_challengedEscrowDeposit_canNotBeResolved_nodeReclaimsAfterChallengeExpiry() public { + _challengeEscrowDeposit(); + + (, EscrowStatus statusAfterChallenge,,,,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(statusAfterChallenge), uint8(EscrowStatus.DISPUTED), "Should be DISPUTED after challenge"); + + vm.warp(block.timestamp + EscrowDepositEngine.CHALLENGE_DURATION + 1); + + uint256 aliceBalanceBefore = token.balanceOf(alice); + + // Unilateral finalization: anyone can call, state is ignored + vm.prank(node); + cHub.finalizeEscrowDeposit(channelId, escrowId, initiateEscrowDepositState); + + (, EscrowStatus statusAfterFinalize,,, uint256 lockedAmount,) = cHub.getEscrowDepositData(escrowId); + assertEq(uint8(statusAfterFinalize), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(lockedAmount, 0, "Locked amount should be 0 after finalization"); + + // Deposit Escrow funds are withdrawn to user wallet + assertEq(token.balanceOf(alice), aliceBalanceBefore + ESCROW_AMOUNT, "User should receive locked amount"); + } + + function test_revert_challengeEscrowDeposit_alreadyChallenged() public { + _challengeEscrowDeposit(); + + // Attempt to challenge the same escrow deposit again + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowDepositState, NODE_PK); + vm.prank(node); + vm.expectRevert(EscrowDepositEngine.IncorrectEscrowStatus.selector); + cHub.challengeEscrowDeposit(escrowId, challengerSig, ParticipantIndex.NODE); + } +} + +contract ChannelHubTest_Challenge_NonHomeChain_EscrowWithdrawal is ChannelHubTest_Challenge_Base { + /* + - reverts on challenging NON-EXISTENT escrow withdrawal + - escrow withdrawal can be challenged + - challenged escrow withdrawal can be resolved until `challengeExpireAt` time has passed with a newer finalization state, which removes challenge and unlock funds + - challenged escrow withdrawal can NOT be resolved if `challengeExpireAt` has passed, but + can be withdrawn after `challengeExpireAt` time passes + - reverts on challenging already challenged escrow withdrawal + */ + + uint64 constant WITHDRAWAL_VERSION = 1; + uint256 constant WITHDRAWAL_AMOUNT = 300; + + bytes32 escrowId; + State initiateEscrowWithdrawalState; + State finalizeEscrowWithdrawalState; + + function setUp() public override { + super.setUp(); + // `def` and `channelId` are set by ChannelHubTest_Challenge_Base.setUp() + // For non-home chain: NON_HOME_CHAIN_ID (42) is the home chain, block.chainid is non-home + + initiateEscrowWithdrawalState = State({ + version: WITHDRAWAL_VERSION, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, // 42 — this IS the home chain (not current chain) + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: 500, // user has enough allocation to withdraw + userNetFlow: 500, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), // current chain is non-home + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: WITHDRAWAL_AMOUNT, // node locks this amount for user's withdrawal + nodeNetFlow: int256(WITHDRAWAL_AMOUNT) + }), + userSig: "", + nodeSig: "" + }); + initiateEscrowWithdrawalState = + mutualSignStateBothWithEcdsaValidator(initiateEscrowWithdrawalState, channelId, ALICE_PK); + + vm.prank(alice); + cHub.initiateEscrowWithdrawal(def, initiateEscrowWithdrawalState); + + escrowId = Utils.getEscrowId(channelId, WITHDRAWAL_VERSION); + + // Finalize state (version = WITHDRAWAL_VERSION + 1): + // home: userAllocation decreases by WITHDRAWAL_AMOUNT, nodeNetFlow decreases by WITHDRAWAL_AMOUNT + // non-home: allocations = 0; userNetFlow = -WITHDRAWAL_AMOUNT, nodeNetFlow = +WITHDRAWAL_AMOUNT + finalizeEscrowWithdrawalState = nextState( + initiateEscrowWithdrawalState, + StateIntent.FINALIZE_ESCROW_WITHDRAWAL, + [uint256(500 - WITHDRAWAL_AMOUNT), uint256(0)], + [int256(500), -int256(WITHDRAWAL_AMOUNT)], + uint64(block.chainid), + address(token), + [uint256(0), uint256(0)], + [-int256(WITHDRAWAL_AMOUNT), int256(WITHDRAWAL_AMOUNT)] + ); + finalizeEscrowWithdrawalState = + mutualSignStateBothWithEcdsaValidator(finalizeEscrowWithdrawalState, channelId, ALICE_PK); + } + + function _challengeEscrowWithdrawal() internal { + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + vm.prank(node); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.NODE); + } + + function test_revert_challengeEscrowWithdrawal_nonExistentEscrow() public { + bytes32 nonExistentEscrowId = Utils.getEscrowId(channelId, 999); + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + vm.prank(node); + vm.expectRevert(ChannelHub.NoChannelIdFoundForEscrow.selector); + cHub.challengeEscrowWithdrawal(nonExistentEscrowId, challengerSig, ParticipantIndex.NODE); + } + + function test_challengeEscrowWithdrawal() public { + _challengeEscrowWithdrawal(); + + (, EscrowStatus status, uint64 challengeExpireAt,,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(status), uint8(EscrowStatus.DISPUTED), "Escrow should be DISPUTED after challenge"); + assertEq( + challengeExpireAt, + uint64(block.timestamp) + EscrowWithdrawalEngine.CHALLENGE_DURATION, + "challengeExpireAt should be set to timestamp + CHALLENGE_DURATION" + ); + } + + function test_resolveChallengedEscrowWithdrawal_withFinalizeState_beforeChallengeExpiry() public { + _challengeEscrowWithdrawal(); + + (, EscrowStatus statusAfterChallenge,,,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(statusAfterChallenge), uint8(EscrowStatus.DISPUTED), "Should be DISPUTED after challenge"); + + uint256 aliceBalanceBefore = token.balanceOf(alice); + uint256 nodeVaultBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); + + // Cooperative finalization with FINALIZE state (before challengeExpireAt) + vm.prank(node); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, finalizeEscrowWithdrawalState); + + (, EscrowStatus statusAfterFinalize,, uint256 lockedAmount,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(statusAfterFinalize), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(lockedAmount, 0, "Locked amount should be 0 after finalization"); + + // Cooperative path: locked funds released to user wallet (withdrawal succeeded) + assertEq( + token.balanceOf(alice), aliceBalanceBefore + WITHDRAWAL_AMOUNT, "User should receive withdrawal amount" + ); + // Node vault should be unchanged (locked amount was already deducted at initiation) + assertEq( + cHub.getAccountBalance(node, address(token), SUB_ID_0), nodeVaultBefore, "Node vault should be unchanged" + ); + } + + function test_challengedEscrowWithdrawal_canNotBeResolved_nodeReclaimsAfterChallengeExpiry() public { + _challengeEscrowWithdrawal(); + + vm.warp(block.timestamp + EscrowWithdrawalEngine.CHALLENGE_DURATION + 1); + + uint256 aliceBalanceBefore = token.balanceOf(alice); + uint256 nodeVaultBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); + + // Attempt cooperative resolution with a valid FINALIZE state after challengeExpireAt + // The unilateral path intercepts and ignores the candidate state + vm.prank(node); + cHub.finalizeEscrowWithdrawal(channelId, escrowId, finalizeEscrowWithdrawalState); + + (, EscrowStatus status,, uint256 lockedAmount,) = cHub.getEscrowWithdrawalData(escrowId); + assertEq(uint8(status), uint8(EscrowStatus.FINALIZED), "Escrow should be FINALIZED"); + assertEq(lockedAmount, 0, "Locked amount should be 0"); + + // Unilateral path (not cooperative): locked funds returned to node vault (withdrawal failed) + assertEq( + cHub.getAccountBalance(node, address(token), SUB_ID_0), + nodeVaultBefore + WITHDRAWAL_AMOUNT, + "Node vault should reclaim locked amount (cooperative resolution bypassed)" + ); + assertEq(token.balanceOf(alice), aliceBalanceBefore, "User wallet unchanged: withdrawal was not completed"); + } + + function test_revert_challengeEscrowWithdrawal_alreadyChallenged() public { + _challengeEscrowWithdrawal(); + + // Attempt to challenge the same escrow withdrawal again + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(channelId, initiateEscrowWithdrawalState, NODE_PK); + vm.prank(node); + vm.expectRevert(EscrowWithdrawalEngine.IncorrectEscrowStatus.selector); + cHub.challengeEscrowWithdrawal(escrowId, challengerSig, ParticipantIndex.NODE); + } +} + +contract ChannelHubTest_Challenge_NonHomeChain_HomeMigration is ChannelHubTest_Challenge_Base { + /* + Test cases: + - a channel in Migrating_in status (empty channel after being called with `initiateMigration`) can be challenged with it + - a channel in Migrating_in status (empty channel after being called with `initiateMigration`) can be challenged with a newer Operation state + */ + + uint64 initiateMigrationVersion = 1; + State initiateMigrationState; + uint64 finalizeMigrationVersion = 2; + State finalizeMigrationState; + uint64 operateAfterMigrationInitVersion = 2; + State operateAfterMigrationInitState; + + // New channel for testing NEW home chain behavior + ChannelDefinition newHomeDef; + bytes32 newHomeChannelId; + State newHomeInitiateMigrationState; + uint64 newHomeOperateVersion = 3; + State newHomeOperateState; + + function setUp() public override { + super.setUp(); + + // Setup for NEW home chain tests (migration IN) + newHomeDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: uint64(42), // Different nonce to create a new channel + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + newHomeChannelId = Utils.getChannelId(newHomeDef, CHANNEL_HUB_VERSION); + + // INITIATE_MIGRATION state for NEW home chain (migration IN) + // homeLedger = OLD home chain (NON_HOME_CHAIN_ID) + // nonHomeLedger = NEW home chain (current chain) + newHomeInitiateMigrationState = State({ + version: initiateMigrationVersion, + intent: StateIntent.INITIATE_MIGRATION, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: NON_HOME_CHAIN_ID, + token: NON_HOME_TOKEN, + decimals: 18, + userAllocation: 500, + userNetFlow: 500, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 500, // Node locks user allocation on new home + nodeNetFlow: 500 + }), + userSig: "", + nodeSig: "" + }); + newHomeInitiateMigrationState = + mutualSignStateBothWithEcdsaValidator(newHomeInitiateMigrationState, newHomeChannelId, ALICE_PK); + + // OPERATE state on NEW home chain after migration + // After initiateMigration on NEW home, ledgers are swapped, so homeLedger becomes current chain + // OPERATE requires userNfDelta == 0, so userNetFlow must stay 0 + newHomeOperateState = State({ + version: newHomeOperateVersion, + intent: StateIntent.OPERATE, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 450, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 450 + }), + nonHomeLedger: Ledger({ + chainId: 0, + token: address(0), + decimals: 0, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + newHomeOperateState = mutualSignStateBothWithEcdsaValidator(newHomeOperateState, newHomeChannelId, ALICE_PK); + } + + function test_challenge_newHomeChain_withInitiateMigration_asExisting() public { + // Initiate migration IN on NEW home chain + vm.prank(alice); + cHub.initiateMigration(newHomeDef, newHomeInitiateMigrationState); + + // Verify channel is in MIGRATING_IN status + verifyChannelData( + newHomeChannelId, + ChannelStatus.MIGRATING_IN, + initiateMigrationVersion, + 0, + "newHomeInitiateMigrationState should be enforced" + ); + + // Challenge with the same INITIATE_MIGRATION state (already enforced) + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(newHomeChannelId, newHomeInitiateMigrationState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(newHomeChannelId, newHomeInitiateMigrationState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and state is still version 0 + verifyChannelData( + newHomeChannelId, + ChannelStatus.DISPUTED, + initiateMigrationVersion, + block.timestamp + CHALLENGE_DURATION, + "initiateMigrationVersion should remain enforced" + ); + } + + function test_challenge_newHomeChain_withOperate_inMigratingIn() public { + // Initiate migration IN on NEW home chain + vm.prank(alice); + cHub.initiateMigration(newHomeDef, newHomeInitiateMigrationState); + + // Verify channel is in MIGRATING_IN status + verifyChannelData( + newHomeChannelId, + ChannelStatus.MIGRATING_IN, + initiateMigrationVersion, + 0, + "newHomeInitiateMigrationState should be enforced" + ); + + // Challenge with newer OPERATE state + bytes memory challengerSig = + signChallengeEip191WithEcdsaValidator(newHomeChannelId, newHomeOperateState, NODE_PK); + + vm.prank(node); + cHub.challengeChannel(newHomeChannelId, newHomeOperateState, challengerSig, ParticipantIndex.NODE); + + // Verify channel is DISPUTED and newHomeOperateState was enforced + verifyChannelData( + newHomeChannelId, + ChannelStatus.DISPUTED, + newHomeOperateVersion, + block.timestamp + CHALLENGE_DURATION, + "newHomeOperateState should start a challenge" + ); + verifyChannelState( + newHomeChannelId, + [uint256(450), uint256(0)], + [int256(0), int256(450)], + "newHomeOperateState should be enforced" + ); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_claimFunds.t.sol b/contracts/test/ChannelHub_claimFunds.t.sol index a1bb3886a..0fea484ac 100644 --- a/contracts/test/ChannelHub_claimFunds.t.sol +++ b/contracts/test/ChannelHub_claimFunds.t.sol @@ -20,6 +20,8 @@ contract ChannelHubTest_claimFunds is Test { address public claimer; address public destination; + uint48 SUB_ID_0 = 0; + uint256 constant RECLAIM_AMOUNT = 100 ether; uint256 constant BALANCE_AMOUNT = RECLAIM_AMOUNT * 10; @@ -61,10 +63,10 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(token), RECLAIM_AMOUNT); vm.expectEmit(true, true, true, true); - emit ChannelHub.FundsClaimed(claimer, address(token), claimer, RECLAIM_AMOUNT); + emit ChannelHub.FundsClaimed(claimer, address(token), 0, claimer, RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(token), claimer); + cHub.claimFunds(address(token), SUB_ID_0, claimer); _verifyTransferSuccess(claimer, claimer, address(token), RECLAIM_AMOUNT); } @@ -73,10 +75,10 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(token), RECLAIM_AMOUNT); vm.expectEmit(true, true, true, true); - emit ChannelHub.FundsClaimed(claimer, address(token), destination, RECLAIM_AMOUNT); + emit ChannelHub.FundsClaimed(claimer, address(token), SUB_ID_0, destination, RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(token), destination); + cHub.claimFunds(address(token), SUB_ID_0, destination); _verifyTransferSuccess(claimer, destination, address(token), RECLAIM_AMOUNT); } @@ -87,7 +89,7 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(token), totalAccumulated); vm.prank(claimer); - cHub.claimFunds(address(token), destination); + cHub.claimFunds(address(token), SUB_ID_0, destination); _verifyTransferSuccess(claimer, destination, address(token), totalAccumulated); } @@ -98,10 +100,10 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(0), RECLAIM_AMOUNT); vm.expectEmit(true, true, true, true); - emit ChannelHub.FundsClaimed(claimer, address(0), claimer, RECLAIM_AMOUNT); + emit ChannelHub.FundsClaimed(claimer, address(0), SUB_ID_0, claimer, RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(0), claimer); + cHub.claimFunds(address(0), SUB_ID_0, claimer); _verifyTransferSuccess(claimer, claimer, address(0), RECLAIM_AMOUNT); } @@ -110,10 +112,10 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(0), RECLAIM_AMOUNT); vm.expectEmit(true, true, true, true); - emit ChannelHub.FundsClaimed(claimer, address(0), destination, RECLAIM_AMOUNT); + emit ChannelHub.FundsClaimed(claimer, address(0), SUB_ID_0, destination, RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(0), destination); + cHub.claimFunds(address(0), SUB_ID_0, destination); _verifyTransferSuccess(claimer, destination, address(0), RECLAIM_AMOUNT); } @@ -125,7 +127,7 @@ contract ChannelHubTest_claimFunds is Test { vm.prank(claimer); vm.expectRevert(ChannelHub.InvalidAddress.selector); - cHub.claimFunds(address(token), address(0)); + cHub.claimFunds(address(token), SUB_ID_0, address(0)); } function test_revert_ifReclaimBalanceIsZero() public { @@ -133,7 +135,7 @@ contract ChannelHubTest_claimFunds is Test { vm.prank(claimer); vm.expectRevert(ChannelHub.IncorrectAmount.selector); - cHub.claimFunds(address(token), destination); + cHub.claimFunds(address(token), SUB_ID_0, destination); } function test_revert_ifETHTransferFails() public { @@ -143,7 +145,7 @@ contract ChannelHubTest_claimFunds is Test { vm.expectRevert( abi.encodeWithSelector(ChannelHub.NativeTransferFailed.selector, address(revertingReceiver), RECLAIM_AMOUNT) ); - cHub.claimFunds(address(0), address(revertingReceiver)); + cHub.claimFunds(address(0), SUB_ID_0, address(revertingReceiver)); } // ========== State Change Tests ========== @@ -156,7 +158,7 @@ contract ChannelHubTest_claimFunds is Test { // Other user tries to claim vm.prank(otherUser); vm.expectRevert(ChannelHub.IncorrectAmount.selector); - cHub.claimFunds(address(token), destination); + cHub.claimFunds(address(token), SUB_ID_0, destination); // Verify reclaim still exists for claimer assertEq(cHub.getReclaimBalance(claimer, address(token)), RECLAIM_AMOUNT, "Reclaim should still exist"); @@ -170,12 +172,12 @@ contract ChannelHubTest_claimFunds is Test { cHub.workaround_setReclaim(claimer, address(token2), RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(token), destination); + cHub.claimFunds(address(token), SUB_ID_0, destination); _verifyTransferSuccess(claimer, destination, address(token), RECLAIM_AMOUNT); vm.prank(claimer); - cHub.claimFunds(address(token2), destination); + cHub.claimFunds(address(token2), SUB_ID_0, destination); _verifyTransferSuccess(claimer, destination, address(token2), RECLAIM_AMOUNT); } diff --git a/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol b/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol new file mode 100644 index 000000000..a3c49e711 --- /dev/null +++ b/contracts/test/ChannelHub_emitsNodeBalanceUpdated.t.sol @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import {Vm} from "forge-std/Vm.sol"; + +import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; + +import {Utils} from "../src/Utils.sol"; +import {ChannelHub} from "../src/ChannelHub.sol"; +import {ChannelDefinition, State, StateIntent, Ledger, ParticipantIndex} from "../src/interfaces/Types.sol"; +import {EscrowWithdrawalEngine} from "../src/EscrowWithdrawalEngine.sol"; + +/** + * Black-box tests verifying that NodeBalanceUpdated is emitted on every operation + * that mutates internal node vault balance (_nodeBalances), and is NOT emitted when + * no mutation occurs. + * + * # Scope + * + * "Node balance" here means the internal vault balance tracked by _nodeBalances, + * i.e. the value returned by getAccountBalance(). It does NOT include funds pushed + * directly to the node's address (e.g. nodeAllocation paid out on channel close), + * because those bypass the vault and require no event. + * + * # Off-chain batching + * + * The protocol allows multiple off-chain transfers to be batched into a single on-chain + * state update (checkpoint). From the contract's perspective this is indistinguishable + * from a single transfer of the same net amount. Batching correctness is an + * off-chain concern and belongs in off-chain unit tests. + */ + +// forge-lint: disable-start(unsafe-typecast) +contract ChannelHubTest_emitsNodeBalanceUpdated is ChannelHubTest_Base { + /** + * Emits NodeBalanceUpdated: + * - depositToVault — direct vault deposit by node + * - withdrawFromVault — direct vault withdrawal by node + * - createChannel (DEPOSIT intent, both lock) — node locks funds into channel + * - createChannel (WITHDRAW intent) — node locks funds into channel + * - depositToChannel (both lock) — node locks funds into channel + * - withdrawFromChannel — node unlocks funds from channel + * - checkpoint (with node fund change) — node balance changes due to off-chain transfer(s) + * - closeChannel cooperative (CLOSE intent) — node unlocks funds from channel + * - challengeChannel with newer state — when newer state carries non-zero node delta + * - initiateEscrowWithdrawal (non-home chain) — node locks liquidity for cross-chain withdrawal + * - finalizeEscrowDeposit (non-home chain) — node releases locked liquidity after swap + * - finalizeEscrowWithdrawal (non-home chain, timeout) — node reclaims locked liquidity after challenge timeout + * - purgeEscrowDeposits — expired escrow deposits released back to node vault + * + * Does NOT emit NodeBalanceUpdated: + * - createChannel (DEPOSIT intent, only user deposits) - status change only, no node fund movement + * - depositToChannel (no change from Node) - no fund movement + * - checkpoint with no node fund change - no fund movement + * - initiateEscrowDeposit (non-home chain) — status change only, no fund movement + * - challengeEscrowDeposit — status change only, no fund movement + * - challengeEscrowWithdrawal — status change only, no fund movement + */ + // ======== State ======== + + ChannelDefinition internal def; + bytes32 internal channelId; + + // Used for non-home chain escrow tests (bob = user, node = node) + ChannelDefinition internal bobDef; + bytes32 internal bobChannelId; + + bytes32 constant NODE_BALANCE_UPDATED_SIG = keccak256("NodeBalanceUpdated(address,address,uint48,uint256)"); + + // Non-home chain constants (fake foreign chain) + uint64 constant FOREIGN_CHAIN_ID = 42; + address constant FOREIGN_TOKEN = address(42); + + Ledger EMPTY_LEDGER = Ledger({chainId: 0, token: address(0), decimals: 0, userAllocation: 0, userNetFlow: 0, nodeAllocation: 0, nodeNetFlow: 0}); + + // ======== Setup ======== + + function setUp() public override { + super.setUp(); + + def = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: alice, + node: node, + nonce: NONCE, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + channelId = Utils.getChannelId(def, CHANNEL_HUB_VERSION); + + bobDef = ChannelDefinition({ + challengeDuration: CHALLENGE_DURATION, + user: bob, + node: node, + nonce: NONCE, + approvedSignatureValidators: 0, + metadata: bytes32(0) + }); + bobChannelId = Utils.getChannelId(bobDef, CHANNEL_HUB_VERSION); + } + + // ======== Helpers ======== + + /// @dev Expects the next NodeBalanceUpdated(node, token, expectedBalance) emission. + function _expectEmitNodeBalanceUpdated(uint256 expectedBalance) internal { + vm.expectEmit(true, true, true, true, address(cHub)); + emit ChannelHub.NodeBalanceUpdated(node, address(token), SUB_ID_0, expectedBalance); + } + + /// @dev Asserts NodeBalanceUpdated was NOT emitted in the logs recorded since the last vm.recordLogs(). + function _assertNoEmitNodeBalanceUpdated() internal view { + Vm.Log[] memory logs = vm.getRecordedLogs(); + for (uint256 i = 0; i < logs.length; i++) { + assertNotEq(logs[i].topics[0], NODE_BALANCE_UPDATED_SIG, "NodeBalanceUpdated was unexpectedly emitted"); + } + } + + /// @dev Creates a channel for alice where node contributes nothing (nodeNetFlow = 0). + /// Returns the signed initial state. + function _createSimpleChannel() internal returns (State memory state) { + state = State({ + version: 0, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + vm.prank(alice); + cHub.createChannel(def, state); + } + + /// @dev Creates a channel via OPERATE intent where node locks DEPOSIT_AMOUNT from vault. + /// Returns the signed initial state. + function _createChannelNodeLocks() internal returns (State memory state) { + state = State({ + version: 0, + intent: StateIntent.OPERATE, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + vm.prank(alice); + cHub.createChannel(def, state); + } + + /// @dev Sets up an escrow deposit on the non-home chain for bob (current chain = non-home). + /// Returns (escrowId, initState). Node vault unchanged; bob's DEPOSIT_AMOUNT is locked. + function _initiateEscrowDeposit() internal returns (bytes32 escrowId, State memory initState) { + initState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: FOREIGN_CHAIN_ID, + token: FOREIGN_TOKEN, + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: DEPOSIT_AMOUNT, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, bobChannelId, BOB_PK); + escrowId = Utils.getEscrowId(bobChannelId, initState.version); + vm.prank(bob); + cHub.initiateEscrowDeposit(bobDef, initState); + } + + /// @dev Sets up an escrow withdrawal on the non-home chain for bob. + /// Returns (escrowId, initState). Node locks DEPOSIT_AMOUNT from vault. + function _initiateEscrowWithdrawal() internal returns (bytes32 escrowId, State memory initState) { + initState = State({ + version: 1, + intent: StateIntent.INITIATE_ESCROW_WITHDRAWAL, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: FOREIGN_CHAIN_ID, + token: FOREIGN_TOKEN, + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: DEPOSIT_AMOUNT, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + userSig: "", + nodeSig: "" + }); + initState = mutualSignStateBothWithEcdsaValidator(initState, bobChannelId, BOB_PK); + escrowId = Utils.getEscrowId(bobChannelId, initState.version); + vm.prank(bob); + cHub.initiateEscrowWithdrawal(bobDef, initState); + } + + // ======== Tests: emits NodeBalanceUpdated ======== + + function test_success_onDepositToVault() public { + token.mint(node, DEPOSIT_AMOUNT); + vm.startPrank(node); + token.approve(address(cHub), DEPOSIT_AMOUNT); + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE + DEPOSIT_AMOUNT); + cHub.depositToVault(node, address(token), SUB_ID_0, DEPOSIT_AMOUNT); + vm.stopPrank(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE + DEPOSIT_AMOUNT); + } + + function test_success_onWithdrawFromVault() public { + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(node); + cHub.withdrawFromVault(node, address(token), SUB_ID_0, DEPOSIT_AMOUNT); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } + + function test_success_onCreateChannel_depositIntent_bothDeposit() public { + // both deposit + State memory state = State({ + version: 0, + intent: StateIntent.DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: DEPOSIT_AMOUNT, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.createChannel(def, state); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } + + function test_success_onCreateChannel_withdrawIntent() public { + // both deposit, node immediately transfers some funds for user to withdraw + State memory state = State({ + version: 0, + intent: StateIntent.WITHDRAW, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 500, + userNetFlow: -500, + nodeAllocation: 0, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + state = mutualSignStateBothWithEcdsaValidator(state, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.createChannel(def, state); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } + + function test_success_onDepositToChannel_bothDeposit() public { + // Setup: channel with user=DA, node=0 + State memory prevState = _createSimpleChannel(); + + // Deposit: both User and Node specify amounts + State memory candidate = nextState( + prevState, + StateIntent.DEPOSIT, + [DEPOSIT_AMOUNT * 2, DEPOSIT_AMOUNT], + [int256(DEPOSIT_AMOUNT) * 2, int256(DEPOSIT_AMOUNT)] + ); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + vm.prank(alice); + cHub.depositToChannel(channelId, candidate); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } + + function test_success_onWithdrawFromChannel() public { + // Setup: channel via OPERATE where node locks DEPOSIT_AMOUNT (vault = INITIAL_BALANCE - DA) + State memory prevState = _createChannelNodeLocks(); + + // User withdraws 500 + State memory candidate = State({ + version: prevState.version + 1, + intent: StateIntent.WITHDRAW, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: -500, + nodeAllocation: 0, + nodeNetFlow: 500 + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + uint256 expectedBalance = INITIAL_BALANCE - DEPOSIT_AMOUNT + 500; + _expectEmitNodeBalanceUpdated(expectedBalance); + vm.prank(alice); + cHub.withdrawFromChannel(channelId, candidate); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), expectedBalance); + } + + function test_success_onCheckpointChannel_withNodeFundChange() public { + State memory prevState = _createSimpleChannel(); + + // Off-chain: user transferred 500 to node. + State memory candidate = nextState(prevState, StateIntent.OPERATE, [DEPOSIT_AMOUNT - 500, 0], [int256(DEPOSIT_AMOUNT), -500]); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + uint256 expectedBalance = INITIAL_BALANCE + 500; + _expectEmitNodeBalanceUpdated(expectedBalance); + vm.prank(alice); + cHub.checkpointChannel(channelId, candidate); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), expectedBalance); + } + + function test_success_onCloseChannel() public { + // Setup: channel via OPERATE where node locks DEPOSIT_AMOUNT (vault = INITIAL_BALANCE - DEPOSIT_AMOUNT) + State memory prevState = _createChannelNodeLocks(); + + // Close: node balance returns to initial balance + State memory candidate = State({ + version: prevState.version + 1, + intent: StateIntent.CLOSE, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: 0 + }), + nonHomeLedger: EMPTY_LEDGER, + userSig: "", + nodeSig: "" + }); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE); + vm.prank(alice); + cHub.closeChannel(channelId, candidate); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_success_onChallengeChannel_newerStateChangesNodeFunds() public { + // Setup: simple channel; node vault = INITIAL_BALANCE, lockedFunds = DEPOSIT_AMOUNT (user's) + State memory initState = _createSimpleChannel(); + + // Off-chain: user transferred 500 to node (nodeNF goes from 0 to -500) + // Enforce via challenge: nodeFundsDelta = -500 - 0 = -500 → vault += 500 + State memory stateV1 = nextState(initState, StateIntent.OPERATE, [DEPOSIT_AMOUNT - 500, uint256(0)], [int256(DEPOSIT_AMOUNT), -500]); + stateV1 = mutualSignStateBothWithEcdsaValidator(stateV1, channelId, ALICE_PK); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(channelId, stateV1, NODE_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE + 500); + vm.prank(node); + cHub.challengeChannel(channelId, stateV1, sig, ParticipantIndex.NODE); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE + 500); + } + + function test_success_onInitiateEscrowWithdrawal_nonHome() public { + // Non-home chain (current): node locks DEPOSIT_AMOUNT from vault to fund user withdrawal + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE - DEPOSIT_AMOUNT); + _initiateEscrowWithdrawal(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } + + function test_success_onFinalizeEscrowDeposit_nonHome() public { + // Setup: bob deposits DEPOSIT_AMOUNT into escrow (node vault unchanged = INITIAL_BALANCE) + (bytes32 escrowId, State memory initState) = _initiateEscrowDeposit(); + + // Finalize: DEPOSIT_AMOUNT flows from escrow (user's locked funds) to node vault + State memory finalizeState = State({ + version: initState.version + 1, + intent: StateIntent.FINALIZE_ESCROW_DEPOSIT, + metadata: bytes32(0), + homeLedger: Ledger({ + chainId: FOREIGN_CHAIN_ID, + token: FOREIGN_TOKEN, + decimals: 18, + userAllocation: DEPOSIT_AMOUNT, + userNetFlow: 0, + nodeAllocation: 0, + nodeNetFlow: int256(DEPOSIT_AMOUNT) + }), + nonHomeLedger: Ledger({ + chainId: uint64(block.chainid), + token: address(token), + decimals: 18, + userAllocation: 0, + userNetFlow: int256(DEPOSIT_AMOUNT), + nodeAllocation: 0, + nodeNetFlow: -int256(DEPOSIT_AMOUNT) + }), + userSig: "", + nodeSig: "" + }); + finalizeState = mutualSignStateBothWithEcdsaValidator(finalizeState, bobChannelId, BOB_PK); + + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE + DEPOSIT_AMOUNT); + vm.prank(node); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, finalizeState); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE + DEPOSIT_AMOUNT); + } + + function test_success_onFinalizeEscrowWithdrawal_nonHome_afterChallengeTimeout() public { + // Setup: node locks DEPOSIT_AMOUNT → vault = INITIAL_BALANCE - DA + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawal(); + + // Challenge: INITIALIZED → DISPUTED + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + vm.prank(bob); + cHub.challengeEscrowWithdrawal(escrowId, sig, ParticipantIndex.USER); + + // Expire the challenge + vm.warp(block.timestamp + EscrowWithdrawalEngine.CHALLENGE_DURATION + 1); + + // Finalize via timeout: node reclaims DEPOSIT_AMOUNT → vault = INITIAL_BALANCE + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE); + vm.prank(node); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, initState); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_success_onPurgeEscrowDeposits() public { + // Setup: bob deposits DEPOSIT_AMOUNT into escrow (node vault unchanged = INITIAL_BALANCE) + _initiateEscrowDeposit(); + + // Wait past unlock delay: escrow becomes unlockable + vm.warp(block.timestamp + cHub.ESCROW_DEPOSIT_UNLOCK_DELAY() + 1); + + // Purge: DEPOSIT_AMOUNT flows from expired escrow to node vault + _expectEmitNodeBalanceUpdated(INITIAL_BALANCE + DEPOSIT_AMOUNT); + cHub.purgeEscrowDeposits(1); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE + DEPOSIT_AMOUNT); + } + + // ======== Tests: does NOT emit NodeBalanceUpdated ======== + + function test_noEmit_onCreateChannel_depositIntent_onlyUserDeposits() public { + // channel is created in _createSimpleChannel; re-verify logs from that call are cleared + vm.recordLogs(); + + _createSimpleChannel(); + + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_noEmit_onDepositToChannel_noNodeChange() public { + // Setup: simple channel, nodeNetFlow = 0 + State memory prevState = _createSimpleChannel(); + + // Deposit: only user adds funds, nodeNetFlow stays at 0 + State memory candidate = nextState(prevState, StateIntent.DEPOSIT, [DEPOSIT_AMOUNT * 2, uint256(0)], [int256(DEPOSIT_AMOUNT) * 2, int256(0)]); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + vm.recordLogs(); + vm.prank(alice); + cHub.depositToChannel(channelId, candidate); + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_noEmit_onCheckpointChannel_noNodeChange() public { + // Setup: simple channel, nodeNetFlow = 0 + State memory prevState = _createSimpleChannel(); + + // Checkpoint: nodeNetFlow stays at 0, userNetFlow unchanged (OPERATE requires userNfDelta == 0) + State memory candidate = nextState(prevState, StateIntent.OPERATE, [DEPOSIT_AMOUNT, uint256(0)], [int256(DEPOSIT_AMOUNT), int256(0)]); + candidate = mutualSignStateBothWithEcdsaValidator(candidate, channelId, ALICE_PK); + + vm.recordLogs(); + vm.prank(alice); + cHub.checkpointChannel(channelId, candidate); + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_noEmit_onInitiateEscrowDeposit_nonHome() public { + // Non-home chain initiate: only user funds move (userFundsDelta > 0, nodeFundsDelta = 0) + vm.recordLogs(); + _initiateEscrowDeposit(); + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_noEmit_onChallengeEscrowDeposit() public { + // Setup: bob deposits DEPOSIT_AMOUNT (node vault = INITIAL_BALANCE, no change) + (bytes32 escrowId, State memory initState) = _initiateEscrowDeposit(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + + vm.recordLogs(); + vm.prank(bob); + cHub.challengeEscrowDeposit(escrowId, sig, ParticipantIndex.USER); + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE); + } + + function test_noEmit_onChallengeEscrowWithdrawal() public { + // Setup: node locks DEPOSIT_AMOUNT (vault = INITIAL_BALANCE - DEPOSIT_AMOUNT) + (bytes32 escrowId, State memory initState) = _initiateEscrowWithdrawal(); + + bytes memory sig = signChallengeEip191WithEcdsaValidator(bobChannelId, initState, BOB_PK); + + vm.recordLogs(); + vm.prank(bob); + cHub.challengeEscrowWithdrawal(escrowId, sig, ParticipantIndex.USER); + _assertNoEmitNodeBalanceUpdated(); + + assertEq(cHub.getAccountBalance(node, address(token), SUB_ID_0), INITIAL_BALANCE - DEPOSIT_AMOUNT); + } +} +// forge-lint: disable-end(unsafe-typecast) diff --git a/contracts/test/ChannelHub_crosschain.lifecycle.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol similarity index 95% rename from contracts/test/ChannelHub_crosschain.lifecycle.t.sol rename to contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol index 9dbfb9bbd..7a77ab324 100644 --- a/contracts/test/ChannelHub_crosschain.lifecycle.t.sol +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_crosschain.lifecycle.t.sol @@ -1,11 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.30; -import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; -import {MockERC20} from "./mocks/MockERC20.sol"; - -import {Utils} from "../src/Utils.sol"; -import {State, ChannelDefinition, StateIntent, Ledger, ChannelStatus, EscrowStatus} from "../src/interfaces/Types.sol"; +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; +import {MockERC20} from "../mocks/MockERC20.sol"; + +import {Utils} from "../../src/Utils.sol"; +import { + State, + ChannelDefinition, + StateIntent, + Ledger, + ChannelStatus, + EscrowStatus +} from "../../src/interfaces/Types.sol"; // forge-lint: disable-next-item(unsafe-typecast) contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { @@ -109,7 +116,9 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // Expected: user allocation = 958, user net flow = 1000, node allocation = 0, node net flow = -42 vm.prank(alice); cHub.initiateEscrowDeposit(def, state); - verifyChannelState(channelId, 958, 1000, 500, 458, "after cross chain deposit"); + verifyChannelState( + channelId, [uint256(958), uint256(500)], [int256(1000), int256(458)], "after cross chain deposit" + ); // finalize escrow deposit state = nextState( @@ -141,7 +150,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { vm.prank(alice); cHub.withdrawFromChannel(channelId, state); - verifyChannelState(channelId, 1220, 750, 0, 470, "after withdrawal"); + verifyChannelState(channelId, [uint256(1220), uint256(0)], [int256(750), int256(470)], "after withdrawal"); // Verify user balance after withdrawal (withdrew 250) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 750, "User balance after withdrawal"); @@ -199,7 +208,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // checkpoint on home chain vm.prank(alice); cHub.checkpointChannel(channelId, state); - verifyChannelState(channelId, 477, 750, 0, -273, "after checkpoint"); + verifyChannelState(channelId, [uint256(477), uint256(0)], [int256(750), int256(-273)], "after checkpoint"); // Verify user balance hasn't changed assertEq(token.balanceOf(alice), INITIAL_BALANCE - 750, "User balance after checkpoint"); @@ -258,7 +267,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { cHub.finalizeMigration(channelId, state); // Verify channel is migrated out - verifyChannelState(channelId, 0, 750, 0, -750, "after migration"); + verifyChannelState(channelId, [uint256(0), uint256(0)], [int256(750), int256(-750)], "after migration"); // Verify user balance hasn't changed (migration doesn't move funds on home chain) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 750, "User balance after migration"); @@ -339,7 +348,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // escrow deposit locked funds should also be unlocked after `unlockAt` time passes alongside any other on-chain call vm.warp(block.timestamp + cHub.ESCROW_DEPOSIT_UNLOCK_DELAY() + 1); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); // state from the "happyPath" test, but with home and nonHome states swapped state = nextState( @@ -356,12 +365,12 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); vm.prank(node); - cHub.finalizeEscrowDeposit(escrowId, state); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, state); // Verify user balance after deposit finalized has NOT changed assertEq(token.balanceOf(bob), INITIAL_BALANCE - 500, "User balance after escrow deposit finalized"); - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token), SUB_ID_0); assertEq(nodeBalanceAfter, nodeBalanceBefore + 500, "Node balance after escrow deposit finalized"); // Verify escrow struct is updated on ChannelsHub @@ -452,7 +461,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { // ====== Finalize escrow deposit ====== vm.warp(block.timestamp + cHub.ESCROW_DEPOSIT_UNLOCK_DELAY() + 1); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token14dec)); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token14dec), SUB_ID_0); // After finalization, home chain user allocation increases, non-home releases funds to node state = nextState( @@ -469,13 +478,13 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); vm.prank(node); - cHub.finalizeEscrowDeposit(escrowId, state); + cHub.finalizeEscrowDeposit(bobChannelId, escrowId, state); // Verify user balance after deposit finalized has NOT changed assertEq(token14dec.balanceOf(bob), 990 * 1e14, "User balance after escrow deposit finalized"); // Verify node received the deposited tokens - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token14dec)); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token14dec), SUB_ID_0); assertEq(nodeBalanceAfter, nodeBalanceBefore + 10 * 1e14, "Node balance after escrow deposit finalized"); // Verify escrow struct is updated on ChannelsHub @@ -493,7 +502,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { (ChannelStatus status,,,,) = cHub.getChannelData(bobChannelId); assertEq(uint8(status), uint8(ChannelStatus.VOID), "Channel should be VOID on non-home chain"); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); // state from the "happyPath" test, but with home and nonHome states swapped State memory state = State({ @@ -533,7 +542,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { cHub.initiateEscrowWithdrawal(bobDef, state); // Verify user node's after deposit (deposited 500) - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token), SUB_ID_0); assertEq(nodeBalanceAfter, nodeBalanceBefore - 750, "Node balance after escrow withdrawal"); // Verify escrow struct is updated on ChannelsHub: escrow data exists, `locked` equals to withdrawalAmount @@ -560,7 +569,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); vm.prank(node); - cHub.finalizeEscrowWithdrawal(escrowId, state); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, state); // Verify user balance after withdrawal (withdrew 750) uint256 bobBalanceAfter = token.balanceOf(bob); @@ -587,10 +596,10 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { vm.startPrank(node); token8dec.mint(node, 100 * 1e8); token8dec.approve(address(cHub), 100 * 1e8); - cHub.depositToVault(node, address(token8dec), 100 * 1e8); + cHub.depositToVault(node, address(token8dec), SUB_ID_0, 100 * 1e8); vm.stopPrank(); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token8dec)); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token8dec), SUB_ID_0); // Bob wants to withdraw 5 tokens on non-home chain (5e8 with 8 decimals = 5e2 with 2 decimals) State memory state = State({ @@ -631,7 +640,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { cHub.initiateEscrowWithdrawal(bobDef, state); // Verify node locked the withdrawal amount - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token8dec)); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token8dec), SUB_ID_0); assertEq(nodeBalanceAfter, nodeBalanceBefore - 5 * 1e8, "Node balance after escrow withdrawal initiation"); // Verify escrow struct is created @@ -660,7 +669,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { state = mutualSignStateBothWithEcdsaValidator(state, bobChannelId, BOB_PK); vm.prank(node); - cHub.finalizeEscrowWithdrawal(escrowId, state); + cHub.finalizeEscrowWithdrawal(bobChannelId, escrowId, state); // Verify user received the withdrawal uint256 bobBalanceAfter = token8dec.balanceOf(bob); @@ -679,7 +688,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { (ChannelStatus status,,,,) = cHub.getChannelData(bobChannelId); assertEq(uint8(status), uint8(ChannelStatus.VOID), "Channel should be VOID on non-home chain"); - uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceBefore = cHub.getAccountBalance(node, address(token), SUB_ID_0); uint256 userBalanceBefore = token.balanceOf(bob); // state from the "happyPath" test @@ -714,7 +723,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { cHub.initiateMigration(bobDef, state); // Verify node's balance after migration (should have locked 469) - uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token)); + uint256 nodeBalanceAfter = cHub.getAccountBalance(node, address(token), SUB_ID_0); assertEq(nodeBalanceAfter, nodeBalanceBefore - 469, "Node balance after migration initiation"); // user balance should not have changed @@ -773,7 +782,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { vm.prank(bob); cHub.withdrawFromChannel(bobChannelId, state); - verifyChannelState(bobChannelId, 61, -400, 0, 461, "after withdrawal"); + verifyChannelState(bobChannelId, [uint256(61), uint256(0)], [int256(-400), int256(461)], "after withdrawal"); // Verify user balance after withdrawal (withdrew 400) assertEq(token.balanceOf(bob), userBalanceBefore + 400, "User balance after withdrawal"); @@ -794,7 +803,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { vm.startPrank(node); token10dec.mint(node, 100 * 1e10); token10dec.approve(address(cHub), 100 * 1e10); - cHub.depositToVault(node, address(token10dec), 100 * 1e10); + cHub.depositToVault(node, address(token10dec), SUB_ID_0, 100 * 1e10); vm.stopPrank(); // 1. Create Channel with 10-decimal token on Old Home Chain @@ -848,7 +857,7 @@ contract ChannelHubTest_CrossChain_Lifecycle is ChannelHubTest_Base { vm.startPrank(node); token14dec.mint(node, 100 * 1e14); token14dec.approve(address(cHub), 100 * 1e14); - cHub.depositToVault(node, address(token14dec), 100 * 1e14); + cHub.depositToVault(node, address(token14dec), SUB_ID_0, 100 * 1e14); vm.stopPrank(); // Initiate migration: Old home has 45 tokens (45e10 with 10 decimals) diff --git a/contracts/test/ChannelHub_singlechain.lifecycle.t.sol b/contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol similarity index 92% rename from contracts/test/ChannelHub_singlechain.lifecycle.t.sol rename to contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol index 28e270261..66b1f186b 100644 --- a/contracts/test/ChannelHub_singlechain.lifecycle.t.sol +++ b/contracts/test/ChannelHub_lifecycle/ChannelHub_singlechain.lifecycle.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.30; -import {ChannelHubTest_Base} from "./ChannelHub_Base.t.sol"; +import {ChannelHubTest_Base} from "../ChannelHub_Base.t.sol"; -import {Utils} from "../src/Utils.sol"; -import {State, ChannelDefinition, StateIntent, Ledger, ChannelStatus} from "../src/interfaces/Types.sol"; -import {SessionKeyAuthorization} from "../src/sigValidators/SessionKeyValidator.sol"; -import {TestUtils, SESSION_KEY_VALIDATOR_ID} from "./TestUtils.sol"; +import {Utils} from "../../src/Utils.sol"; +import {State, ChannelDefinition, StateIntent, Ledger, ChannelStatus} from "../../src/interfaces/Types.sol"; +import {SessionKeyAuthorization} from "../../src/sigValidators/SessionKeyValidator.sol"; +import {TestUtils, SESSION_KEY_VALIDATOR_ID} from "../TestUtils.sol"; contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { function test_happyPath() public { @@ -81,7 +81,7 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { // Expected: user allocation = 958, user net flow = 1000, node allocation = 0, node net flow = -42 vm.prank(alice); cHub.checkpointChannel(channelId, state); - verifyChannelState(channelId, 958, 1000, 0, -42, "after checkpoint"); + verifyChannelState(channelId, [uint256(958), uint256(0)], [int256(1000), int256(-42)], "after checkpoint"); // receive 24 (allocation increases by 24, node net flow increases by 24) state = nextState(state, StateIntent.OPERATE, [uint256(982), uint256(0)], [int256(1000), int256(-18)]); @@ -95,7 +95,7 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { vm.prank(alice); cHub.depositToChannel(channelId, state); - verifyChannelState(channelId, 1482, 1500, 0, -18, "after deposit"); + verifyChannelState(channelId, [uint256(1482), uint256(0)], [int256(1500), int256(-18)], "after deposit"); // Verify user balance after first deposit (deposited 500 more) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1500, "User balance after first deposit"); @@ -116,7 +116,7 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { vm.prank(alice); cHub.withdrawFromChannel(channelId, state); - verifyChannelState(channelId, 1379, 1400, 0, -21, "after withdrawal"); + verifyChannelState(channelId, [uint256(1379), uint256(0)], [int256(1400), int256(-21)], "after withdrawal"); // Verify user balance after first withdrawal (withdrew 100) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1400, "User balance after first withdrawal"); @@ -137,7 +137,7 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { vm.prank(alice); cHub.depositToChannel(channelId, state); - verifyChannelState(channelId, 1586, 1600, 0, -14, "after second deposit"); + verifyChannelState(channelId, [uint256(1586), uint256(0)], [int256(1600), int256(-14)], "after second deposit"); // Verify user balance after second deposit (deposited 200 more) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1600, "User balance after second deposit"); @@ -170,7 +170,9 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { vm.prank(alice); cHub.withdrawFromChannel(channelId, state); - verifyChannelState(channelId, 1289, 1300, 0, -11, "after second withdrawal"); + verifyChannelState( + channelId, [uint256(1289), uint256(0)], [int256(1300), int256(-11)], "after second withdrawal" + ); // Verify user balance after second withdrawal (withdrew 300) assertEq(token.balanceOf(alice), INITIAL_BALANCE - 1300, "User balance after second withdrawal"); @@ -259,7 +261,9 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { cHub.createChannel(def, state); assertEq(token.balanceOf(alice), INITIAL_BALANCE, "User balance after OPERATE creation stays the same"); - verifyChannelState(channelId, 1000, 0, 0, 1000, "after create with OPERATE intent"); + verifyChannelState( + channelId, [uint256(1000), uint256(0)], [int256(0), int256(1000)], "after create with OPERATE intent" + ); (ChannelStatus status,, State memory latestState,, uint256 lockedFunds) = cHub.getChannelData(channelId); assertEq( uint8(status), uint8(ChannelStatus.OPERATING), "Channel created with OPERATE intent should be OPERATING" @@ -324,7 +328,9 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { cHub.createChannel(def, state); assertEq(token.balanceOf(alice), INITIAL_BALANCE - 500, "User balance after DEPOSIT creation decreases"); - verifyChannelState(channelId, 1500, 500, 0, 1000, "after create with DEPOSIT intent"); + verifyChannelState( + channelId, [uint256(1500), uint256(0)], [int256(500), int256(1000)], "after create with DEPOSIT intent" + ); (ChannelStatus status,, State memory latestState,, uint256 lockedFunds) = cHub.getChannelData(channelId); assertEq( uint8(status), uint8(ChannelStatus.OPERATING), "Channel created with DEPOSIT intent should be OPERATING" @@ -389,7 +395,9 @@ contract ChannelHubTest_SingleChain_Lifecycle is ChannelHubTest_Base { cHub.createChannel(def, state); assertEq(token.balanceOf(alice), INITIAL_BALANCE + 500, "User balance after WITHDRAW creation increases"); - verifyChannelState(channelId, 500, -500, 0, 1000, "after create with WITHDRAW intent"); + verifyChannelState( + channelId, [uint256(500), uint256(0)], [int256(-500), int256(1000)], "after create with WITHDRAW intent" + ); (ChannelStatus status,, State memory latestState,, uint256 lockedFunds) = cHub.getChannelData(channelId); assertEq( uint8(status), uint8(ChannelStatus.OPERATING), "Channel created with WITHDRAW intent should be OPERATING" diff --git a/contracts/test/ChannelHub_pushFunds.t.sol b/contracts/test/ChannelHub_pushFunds.t.sol index 8c0f8fac3..576f4c5c7 100644 --- a/contracts/test/ChannelHub_pushFunds.t.sol +++ b/contracts/test/ChannelHub_pushFunds.t.sol @@ -38,6 +38,8 @@ contract ChannelHubTest_pushFunds is Test { address public recipient; + uint48 constant SUB_ID_0 = 0; + uint256 constant TRANSFER_AMOUNT = 1000 ether; uint256 constant BALANCE_AMOUNT = TRANSFER_AMOUNT * 10; @@ -122,7 +124,7 @@ contract ChannelHubTest_pushFunds is Test { function test_accumulatesReclaims_whenERC20Reverts() public { vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(revertingToken), TRANSFER_AMOUNT); + emit ChannelHub.TransferFailed(SUB_ID_0, recipient, address(revertingToken), TRANSFER_AMOUNT); cHub.exposed_pushFunds(recipient, address(revertingToken), TRANSFER_AMOUNT); @@ -141,7 +143,7 @@ contract ChannelHubTest_pushFunds is Test { function test_accumulatesReclaims_whenERC20ConsumesAllGas() public { vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); + emit ChannelHub.TransferFailed(SUB_ID_0, recipient, address(gasConsumingToken), TRANSFER_AMOUNT); cHub.exposed_pushFunds(recipient, address(gasConsumingToken), TRANSFER_AMOUNT); @@ -152,7 +154,7 @@ contract ChannelHubTest_pushFunds is Test { function test_accumulatesReclaims_whenERC20ReturnsMalformedData() public { vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(recipient, address(malformedToken), TRANSFER_AMOUNT); + emit ChannelHub.TransferFailed(SUB_ID_0, recipient, address(malformedToken), TRANSFER_AMOUNT); cHub.exposed_pushFunds(recipient, address(malformedToken), TRANSFER_AMOUNT); @@ -177,7 +179,7 @@ contract ChannelHubTest_pushFunds is Test { function test_accumulatesReclaims_whenETHReceiverReverts() public { vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(address(revertingReceiver), address(0), TRANSFER_AMOUNT); + emit ChannelHub.TransferFailed(SUB_ID_0, address(revertingReceiver), address(0), TRANSFER_AMOUNT); cHub.exposed_pushFunds(address(revertingReceiver), address(0), TRANSFER_AMOUNT); @@ -188,7 +190,7 @@ contract ChannelHubTest_pushFunds is Test { function test_accumulatesReclaims_whenETHReceiverConsumesAllGas() public { vm.expectEmit(true, true, false, true); - emit ChannelHub.TransferFailed(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); + emit ChannelHub.TransferFailed(SUB_ID_0, address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); cHub.exposed_pushFunds(address(gasConsumingReceiver), address(0), TRANSFER_AMOUNT); diff --git a/contracts/test/ParametricToken/ParametricToken.t.sol b/contracts/test/ParametricToken/ParametricToken.t.sol new file mode 100644 index 000000000..753ae99c1 --- /dev/null +++ b/contracts/test/ParametricToken/ParametricToken.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.30; + +import "forge-std/Test.sol"; +import "../../src/ParametricToken.sol"; + +contract ParametricTokenTest is Test { + ParametricToken public token; + address public alice = address(0x1); + address public bob = address(0x2); + address public charlie = address(0x3); + + uint48 constant SUBID0 = 0; + uint48 constant SUBID1 = 1; + uint48 constant SUBID10 = 1000; + + function setUp() public { + token = new ParametricToken("Shortbit", "sBTC"); + + // Alice mint + vm.prank(alice); + vm.warp(1_000_000); + token.mint(1000 ether); + console.log("Alice parameter", token.parameterOf(0, alice)); + vm.stopPrank(); + + // Bob mint + vm.prank(bob); + vm.warp(2_000_000); + token.mint(400 ether); + console.log("Bob parameter after mint 1", token.parameterOf(0, bob)); + + // Bob mint 2 + vm.prank(bob); + vm.warp(4_000_000); + token.mint(100 ether); + console.log("Bob parameter after mint 2", token.parameterOf(0, bob)); + + assertEq(token.parameterOf(0, bob), 2_400_000); + } + + function testNormalTransfer() public { + vm.prank(alice); + token.transfer(bob, 100 ether); + + assertEq(token.balanceOf(alice), 900 ether); + assertEq(token.balanceOf(bob), 600 ether); + } + + function testConvertToSuper() public { + vm.prank(alice); + token.convertToSuper(alice); + + assertEq(uint8(token.accountType(alice)), uint8(IParametricToken.AccountType.Super)); + assertEq(token.balanceOf(alice), 1000 ether); + assertEq(token.balanceOfSub(alice, 0), 1000 ether); + } + + function testCreateSubAccount() public { + vm.startPrank(alice); + token.convertToSuper(alice); + + uint48 subId = token.createSubAccount(alice); + // SubId 3 doesn't exist, should revert + vm.expectRevert("Sub-account doesn't exist"); + token.balanceOfSub(alice, 3); + vm.stopPrank(); + + assertEq(subId, 1); + assertEq(token.subsCountOf(alice), 2); + assertEq(token.balanceOfSub(alice, subId), 0); + } + + function testTransferToSub() public { + // Setup + vm.startPrank(alice); + token.convertToSuper(alice); + uint48 subId = token.createSubAccount(alice); + vm.stopPrank(); + + // Bob transfers to alice's sub-account 0 + vm.startPrank(bob); + token.transferToSub(alice, 0, 100 ether); + token.transferToSub(alice, subId, 50 ether); + vm.stopPrank(); + + assertEq(token.balanceOfSub(alice, 0), 1100 ether); + assertEq(token.balanceOfSub(alice, subId), 50 ether); + assertEq(token.balanceOf(alice), 1150 ether); + assertEq(token.balanceOf(bob), 350 ether); + + console.log("Alice param sub 0", token.parameterOfSub(0, alice, 0)); + console.log("Alice param sub", subId, token.parameterOfSub(0, alice, subId)); + } + + function testTransferFromSub() public { + // Setup: alice becomes super, creates sub, funds it + vm.startPrank(alice); + token.convertToSuper(alice); + uint48 subId = token.createSubAccount(alice); + token.transferBetweenSubs(0, subId, 200 ether); + + // Transfer from sub to bob + token.transferFromSub(subId, bob, 150 ether); + vm.stopPrank(); + + assertEq(token.balanceOfSub(alice, subId), 50 ether); + assertEq(token.balanceOf(bob), 650 ether); + + console.log("Alice param sub 0", token.parameterOfSub(0, alice, 0)); + console.log("Alice param sub", subId, token.parameterOfSub(0, alice, subId)); + console.log("Bob param", token.parameterOf(0, bob)); + } +} diff --git a/contracts/test/TestChannelHub.sol b/contracts/test/TestChannelHub.sol index b27491b86..ce346b8e8 100644 --- a/contracts/test/TestChannelHub.sol +++ b/contracts/test/TestChannelHub.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.30; +pragma solidity ^0.8.30; import {ChannelHub} from "../src/ChannelHub.sol"; import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; @@ -9,6 +9,8 @@ import {ISignatureValidator} from "../src/interfaces/ISignatureValidator.sol"; * @notice Test harness contract that exposes internal ChannelHub functions for testing */ contract TestChannelHub is ChannelHub { + uint48 constant SUB_ID = 0; + constructor(ISignatureValidator _defaultSigValidator) ChannelHub(_defaultSigValidator) {} /** @@ -21,14 +23,14 @@ contract TestChannelHub is ChannelHub { * @notice Exposed version of _pushFunds for testing */ function exposed_pushFunds(address to, address token, uint256 amount) external payable { - _pushFunds(to, token, amount); + _pushFunds(SUB_ID, to, token, amount); } /** * @notice Exposed version of _pullFunds for testing */ function exposed_pullFunds(address from, address token, uint256 amount) external payable { - _pullFunds(from, token, amount); + _pullFunds(from, SUB_ID, token, amount); } /** diff --git a/docs/README.md b/docs/README.md index b05548aa3..e12b36c42 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,76 +1,68 @@ # Nitrolite V1 Clearnode Specifications -This directory introduces new Clearnode architecture, models and communication flows to facilitate communication between user, SDK client, Node and Blockchains that will become the core off-chain engine for the Nitrolite V1 Protocol. +This directory contains Clearnode architecture, models and communication flows that facilitate communication between user, SDK client, Node and Blockchains — the core off-chain engine for the Nitrolite V1 Protocol. ## Contents - **[api.yaml](api.yaml)** - API definitions including types, state transitions, and RPC methods - **[data_models.mmd](data_models.mmd)** - Data model diagrams -- **[rpc_message.md](rpc_message.md)** - Standardized RPC message format for communication with a Clearnode via WebSocket ### Communication Flows -- **[transfer.mmd](communication_flows/transfer.mmd)** - Off-chain transfer flow -- **[app_session_deposit.mmd](communication_flows/app_session_deposit.mmd)** - Application session deposit -- **[escrow_chan_deposit.mmd](communication_flows/escrow_chan_deposit.mmd)** - Escrow channel deposit -- **[escrow_chan_withdrawal.mmd](communication_flows/escrow_chan_withdrawal.mmd)** - Escrow channel withdrawal -- **[home_chan_creation_from_scratch.mmd](communication_flows/home_chan_creation_from_scratch.mmd)** - Home channel creation -- **[home_chan_withdraw.mmd](communication_flows/home_chan_withdraw.mmd)** - Home channel withdrawal -- **[home_chan_withdraw_on_create_from_state.mmd](communication_flows/home_chan_withdraw_on_create_from_state.mmd)** - State-based channel creation with withdrawal +- **[home_chan_creation_from_scratch.mmd](communication_flows/home_chan_creation_from_scratch.mmd)** - Home channel creation with initial deposit +- **[home_chan_deposit.mmd](communication_flows/home_chan_deposit.mmd)** - Home channel deposit (existing channel) +- **[home_chan_withdraw.mmd](communication_flows/home_chan_withdraw.mmd)** - Home channel withdrawal (existing channel) +- **[home_chan_withdraw_on_create_from_state.mmd](communication_flows/home_chan_withdraw_on_create_from_state.mmd)** - Channel creation with withdrawal from pending state +- **[transfer.mmd](communication_flows/transfer.mmd)** - Off-chain transfer (sender + automatic receiver state creation) +- **[app_session_deposit.mmd](communication_flows/app_session_deposit.mmd)** - Application session deposit with quorum verification +- **[escrow_chan_deposit.mmd](communication_flows/escrow_chan_deposit.mmd)** - Cross-chain escrow deposit (mutual lock → on-chain → finalize) +- **[escrow_chan_withdrawal.mmd](communication_flows/escrow_chan_withdrawal.mmd)** - Cross-chain escrow withdrawal (escrow lock → on-chain → finalize) #### Remaining Flows -The following communication flows are not yet documented but will be added in future iterations: +The following communication flows are not yet documented: -- **Remaining app session endpoints** are not affected and will be added here later. The only new requirement includes creating app sessions with 0 allocations, and participants depositing one by one. Now app session deposits are limited to one participant deposit at a time. - -- **home channel deposit** - Similar to home channel creation with deposit, but for existing channels - **home chain migration** - Cross-chain state migration between home channels -- **off-chain transfer to a non-existing user** - Handles receiver account creation during transfer +- **app session create / operate / withdraw / close** - Full app session lifecycle beyond deposits --- -**Note:** This directory contains ongoing work on Nitrolite V1 protocol architecture. - ## Project Structure -The following is a suggested project structure that may change as the implementation evolves: - -```t -cerebro/ +```text +cerebro/ # Cerebro Testing Client clearnode/ - api/ # AppSessionService - app_session/ - channel/ - user/ - node/ + action_gateway/ # Rate limiting via gated actions + api/ + app_session_v1/ # App session endpoints (create, deposit, operate, withdraw, close) + apps_v1/ # Application registry endpoints + channel_v1/ # Channel endpoints (create, submit_state, get_state, transfer) + node_v1/ # Node info endpoints + user_v1/ # User endpoints (balances, staking) config/ - migrations/ # database migration files - postgres/ - sqlite/ - metric/ - prometheus/ # Prometheus metrics exporter + migrations/ + postgres/ # Goose SQL migrations (embedded at compile time) + event_handlers/ # Blockchain event processing (channel events, locking events) + metrics/ # Prometheus metrics + lifespan metric aggregation store/ - db/ # struct Database implements Store interface - memory/ # may include in-memory store for Asset's, Blockchain's etc. - blockchain_worker.go # service: BlockchainWorker, BWStore - config.go - event_handler.go # service: EventHandler - eth_listener.go # service: SmartContractListener, SCLStore (TBD) - main.go # 1st - monolithic clearnode implementation; then - refactor into microservices - rpc_router.go # RPC Router binding RPC methods to handlers -contract/ -docs/ + database/ # GORM-based DB store + memory/ # In-memory store for assets, blockchains, config + blockchain_worker.go # Processes pending BlockchainAction records + runtime.go # Embeds migrations, initializes services + main.go # Entry point, EVM listeners, metric exporters +contracts/ # Smart contracts (ChannelHub, Locking, etc.) +docs/ # This directory pkg/ - amm/ - app_session/ + app/ # App session types (AppSessionStatus, quorum, allocations) blockchain/ - evm/ # Client implementations for EVM-based blockchains - core/ # Client interface (Create, Checkpoint, Challenge etc.), PackState, UnpackState, TransitionValidator, functions related to State build - rpc/ # Node, Client, Requests, Responses, Events, Errors + evm/ # EVM client implementations + core/ # Core types: Channel, State, Transaction, Signer, Transition + log/ # Structured logging + rpc/ # RPC protocol: messages, requests, responses, errors + sign/ # Signer implementations (EthereumMsgSigner, EthereumRawSigner) sdk/ - go/ - ts/ # should include implementations for everything inside /pkg/ -test/ # integration test scenarios executed by all SDKs inside sdk/ directory -go.mod + go/ # Go SDK client + ts/ # TypeScript SDK client + ts-compat/ # TypeScript compatibility SDK +test/ # Integration test scenarios ``` diff --git a/docs/api.yaml b/docs/api.yaml index 886019203..6de9fe49b 100644 --- a/docs/api.yaml +++ b/docs/api.yaml @@ -156,7 +156,7 @@ types: - app_definition: description: Definition for an app session fields: - - name: application + - name: application_id type: string description: Application identifier from an app registry - name: participants @@ -338,24 +338,16 @@ types: - name: status type: string description: Session status (open/closed) - - name: participants - type: array - items: - type: app_participant - description: List of participant wallet addresses with weights + - name: app_definition + type: app_definition + description: The application definition for this session - name: session_data type: string description: JSON stringified session data optional: true - - name: quorum - type: integer - description: Quorum required for operations - name: version type: string description: Current version of the session state - - name: nonce - type: string - description: Nonce for the session - name: allocations type: array items: @@ -415,6 +407,66 @@ types: type: string description: User's signature over the session key metadata to authorize the registration/update of the session key + - app: + description: Application definition + fields: + - name: id + type: string + description: Application identifier + - name: owner_wallet + type: string + description: Owner's wallet address + - name: metadata + type: string + description: Application metadata (bytes32 hash) + - name: version + type: string + description: Current version of the application + - name: creation_approval_not_required + type: boolean + description: Whether app sessions can be created without owner approval + + - app_info: + description: Full application info + fields: + - name: id + type: string + description: Application identifier + - name: owner_wallet + type: string + description: Owner's wallet address + - name: metadata + type: string + description: Application metadata (bytes32 hash) + - name: version + type: string + description: Current version of the application + - name: creation_approval_not_required + type: boolean + description: Whether app sessions can be created without owner approval + - name: created_at + type: string + description: Creation timestamp (unix seconds) + - name: updated_at + type: string + description: Last update timestamp (unix seconds) + + - action_allowance: + description: Allowance information for a specific gated action + fields: + - name: gated_action + type: string + description: The specific action being gated (transfer, app_session_deposit, app_session_operation, app_session_withdrawal) + - name: time_window + type: string + description: Time window for which the allowance is valid (e.g. "24h0m0s") + - name: allowance + type: string + description: Total allowance for the action within the time window + - name: used + type: string + description: Amount already used within the time window + - pagination_params: description: Pagination request parameters fields: @@ -760,7 +812,7 @@ api: - message: invalid_parameters description: The request parameters are invalid - name: create_app_session - description: Create a new application session between participants + description: Create a new application session between participants. The application must be registered in the app registry. If the application requires creation approval (creation_approval_not_required is false), an owner signature is required. request: - field_name: definition type: app_definition @@ -773,6 +825,9 @@ api: description: Participant signatures for the app session creation items: type: string + - field_name: owner_sig + type: string + description: Owner signature for app session creation, required when the application's creation_approval_not_required is false optional: true response: - field_name: app_session_id @@ -787,6 +842,12 @@ api: errors: - message: invalid_definition description: The application definition is invalid + - message: application_not_registered + description: The application is not registered in the app registry + - message: owner_sig_required + description: Owner signature is required for this application (creation_approval_not_required is false) + - message: invalid_owner_signature + description: The owner signature is invalid or does not match the registered app owner - message: insufficient_balance description: Participant has insufficient balance for allocations - name: submit_session_key_state @@ -819,6 +880,58 @@ api: - message: account_not_found description: The specified account was not found + - name: apps + description: Operations related to application registry management + versions: + - version: v1 + methods: + - name: get_apps + description: Retrieve registered applications with optional filtering by app ID and owner wallet + request: + - field_name: app_id + type: string + description: Filter by application ID + optional: true + - field_name: owner_wallet + type: string + description: Filter by owner wallet address + optional: true + - field_name: pagination + type: pagination_params + description: Pagination parameters (offset, limit, sort) + optional: true + response: + - field_name: apps + type: array + items: + type: app_info + description: List of registered applications + - field_name: metadata + type: pagination_metadata + description: Pagination information + errors: + - message: invalid_parameters + description: The request parameters are invalid + - name: submit_app_version + description: Register a new application in the app registry. Currently only version 1 (creation) is supported. The owner must sign the packed app data to prove ownership. + request: + - field_name: app + type: app + description: Application definition including ID, owner wallet, metadata, version, and creation approval flag + - field_name: owner_sig + type: string + description: Owner's EIP-191 signature over the packed application data + response: [] + errors: + - message: invalid_app_id + description: The application ID does not match the required format + - message: invalid_version + description: Only version 1 (creation) is currently supported + - message: invalid_signature + description: The owner signature is invalid + - message: app_already_exists + description: An application with this ID already exists + - name: session_keys description: Operations related to session key management versions: @@ -879,6 +992,23 @@ api: type: pagination_metadata description: Pagination information optional: true + - name: get_action_allowances + description: Retrieve action allowances for a user based on their staking level + request: + - field_name: wallet + type: string + description: User's wallet address + response: + - field_name: allowances + type: array + items: + type: action_allowance + description: List of action allowances + errors: + - message: wallet_required + description: The wallet address is required + - message: retrieval_failed + description: Failed to retrieve action allowances - name: node description: Utility methods to get node's configuration and check connectivity diff --git a/docs/communication_flows/app_session_deposit.mmd b/docs/communication_flows/app_session_deposit.mmd index a8f7cf71e..fe724e339 100644 --- a/docs/communication_flows/app_session_deposit.mmd +++ b/docs/communication_flows/app_session_deposit.mmd @@ -2,49 +2,48 @@ sequenceDiagram actor User actor SenderClient actor Node - + Note over SenderClient: Connected to Node - Note over Node: Contains user's state with Home Chain - - %% { - %% newAppState { - %% app_session_id: appSessionId as Hex, - %% intentDeposit, - %% version, - %% allocations, - %% session_data: JSON.stringify(sessionData), - %% } - %% sigQuorum: [] - %% NewUserState - %% } - - User->>SenderClient: submit_app_state(newAppState,sigQuorum) + Note over Node: Contains user's state with Home Chain + + User->>SenderClient: submit_app_state(newAppState, sigQuorum) SenderClient->>Node: GetAppSessionState(app_session_id) - Node->>SenderClient: Returns an actual app session state + Node->>SenderClient: Returns current app session state SenderClient-->>SenderClient: ValidateSessionAppState(currentAppState, newAppState, sigQuorum) - Note right of SenderClient: intent=deposit, userWallet=appParticipant, only the participant deposits, validate quorum + Note right of SenderClient: intent=deposit, userWallet=appParticipant, validate quorum SenderClient->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>SenderClient: Returns a state with home chain - %% Note over SenderClient: Check current state transitions Note over SenderClient: createNextState(currentState) returns state - Note over SenderClient: state.setID(CalculateStateID(state.userWallet, state.asset, cycleId, state.version)) + Note over SenderClient: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) Note over SenderClient: NewTransition(commitT, state.ID(), appSessionId, amount) Note over SenderClient: state.applyTransitions(transitions) returns true - Note over SenderClient: signState(state) returns userSig + Note over SenderClient: signState(state) returns userSig (prepends signer type byte) SenderClient->>Node: SubmitDepositState(newAppState, sigQuorum, state, userSig) - Note over Node: Perform existing app session state validation steps - Note over Node: GetLastState(userWallet, asset) returns currentState - Note over Node: EnsureNoOngoingTransitions() - Note over Node: ValidateStateTransition(currentState, state) - Note over Node: EnsureSameDepositTokenAmount(newAppState, newUserState) - Note over Node: StoreState(state) - - Node->>SenderClient: Sends AppSessionUpdate - Node->>SenderClient: Return node signature + Note over Node: LockUserState(userWallet, asset) + Note over Node: GetAppSession(appSessionId) → validate exists, open, version matches + Note over Node: Verify intent == Deposit + Note over Node: GetRegisteredApp(applicationId) → validate exists + Note over Node: ActionGateway.AllowAction(appOwner, GatedActionAppSessionDeposit) + Note over Node: GetLastUserState(userWallet, asset) → currentState + Note over Node: CheckOpenChannel(userWallet, asset) → approvedSigValidators + Note over Node: EnsureNoOngoingStateTransitions(userWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: ChannelSigValidator.Verify(userWallet, packedState, userSig) + Note over Node: VerifyQuorum(packedAppState, sigQuorum, participants) + Note over Node: Validate allocations: no negatives, valid participants, asset match + Note over Node: RecordLedgerEntry(participant, sessionID, asset, depositAmount) + Note over Node: Verify total deposit == state transition amount + Note over Node: nodeSigner.Sign(packedState) → nodeSig + Note over Node: StoreUserState(incomingState) → also updates UserBalance + Note over Node: NewTransactionFromTransition(state, transition) → RecordTransaction + Note over Node: Update app session version + store + + Node->>SenderClient: Return node signature (StateNodeSig) SenderClient->>User: Returns success & tx hash diff --git a/docs/communication_flows/escrow_chan_deposit.mmd b/docs/communication_flows/escrow_chan_deposit.mmd index 69cb8a369..7f721d501 100644 --- a/docs/communication_flows/escrow_chan_deposit.mmd +++ b/docs/communication_flows/escrow_chan_deposit.mmd @@ -5,69 +5,83 @@ sequenceDiagram actor HomeChain actor EscrowChain Note over HomeChain: User already has a home channel - Note over Node: Contains user's state with Home Chain + Note over Node: Contains user's state with Home Chain Note right of Client: Connected to Node + User->>Client: async deposit(blockchainId, asset, amount) + %% Phase 1: Mutual Lock - lock funds from home to escrow Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns state Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, state.cycleId, state.version)) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) Note over Client: GetTokenAddress(blockchainId, asset) Note over Client: state.setEscrowToken(blockchainId, tokenAddress) Note over Client: GetEscrowChannelID(homeChannelDef, state.version) - Note over Client: NewTransition(mutualLockT, state.ID(), homeChannelID, amount) + Note over Client: NewTransition(TransitionTypeMutualLock, state.ID(), homeChannelID, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreEscrowChannel(escrow_channel) - Note right of Node: StoreState(state) + Note over Node: LockUserState(userWallet, asset) + Note over Node: CheckOpenChannel(userWallet, asset) → approvedSigValidators + Note over Node: GetLastUserState(userWallet, asset) → currentState + Note over Node: EnsureNoOngoingStateTransitions(userWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState → verify userSig → nodeSigner.Sign(packedState) + Note over Node: StoreEscrowChannel(escrow_channel) + Note over Node: RecordTransaction → StoreUserState(state) Node->>Client: Return node signature + + %% Phase 2: On-chain escrow deposit initiation Note over Client: PackChannelDefinition(channelDef) Note over Client: PackState(channelId, state) Client->>EscrowChain: initiateEscrowDeposit(packedChannelDef, packedState) EscrowChain->>Client: Return Tx Hash EscrowChain-->>Node: Emits EscrowDepositInitiated Event - Note right of Node: HandleEscrowDepositInitiated() - Note right of Node: UpdateEscrowChannel(escrow_channel) + Note over Node: HandleEscrowDepositInitiated() + Note over Node: escrowChannel.StateVersion = event.StateVersion + Note over Node: escrowChannel.Status = Open + Note over Node: ScheduleInitiateEscrowDeposit(state.ID, blockchainId) + Note over Node: UpdateChannel(escrowChannel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate - + + %% Phase 3: Node checkpoints on home chain Node->>HomeChain: checkpoint(homeChannelId, packedState) - HomeChain-->>Node: Emits Checkpointed Event - Node-->>Node: HandleCheckpointed() + HomeChain-->>Node: Emits HomeChannelCheckpointed Event + Note over Node: HandleHomeChannelCheckpointed() + Note over Node: channel.StateVersion = event.StateVersion + Note over Node: UpdateChannel(channel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate - + + %% Phase 4: Escrow deposit finalization - credit home from escrow Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns state Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, state.cycleId, state.version)) - Note over Client: NewTransition(escrow_depositT, state.ID(), homeChannelID, amount) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) + Note over Client: NewTransition(TransitionTypeEscrowDeposit, state.ID(), homeChannelID, amount) Note over Client: state.applyTransitions(transitions) returns true Note over Client: signState(state) returns userSig Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreState(state) + Note over Node: LockUserState → CheckOpenChannel → GetLastUserState + Note over Node: EnsureNoOngoingStateTransitions + Note over Node: ValidateStateAdvancement → PackState → verify sig → node sign + Note over Node: RecordTransaction → StoreUserState(state) Node->>Client: Return node signature Client-->>User: Returns success - Note over Node: Escrowed funds would be released automatically after lock period - Note over Node: If fast unlock is needed, the node can checkpoint on escrow channel. + %% Phase 5: Node finalizes escrow on-chain + Note over Node: Escrowed funds released automatically after lock period + Note over Node: If fast unlock needed, node can checkpoint on escrow channel Note over Node: PackState(channelId, state) Node->>EscrowChain: finalizeEscrowDeposit(escrowChannelId, packedState) EscrowChain->>Node: Return Tx Hash EscrowChain-->>Node: Emits EscrowDepositFinalized Event - Note right of Node: HandleEscrowDepositFinalized() - Note right of Node: UpdateEscrowChannel(escrow_channel) + Note over Node: HandleEscrowDepositFinalized() + Note over Node: escrowChannel.StateVersion = event.StateVersion + Note over Node: escrowChannel.Status = Closed + Note over Node: UpdateChannel(escrowChannel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate - \ No newline at end of file diff --git a/docs/communication_flows/escrow_chan_withdrawal.mmd b/docs/communication_flows/escrow_chan_withdrawal.mmd index cec96b3b4..479d01dad 100644 --- a/docs/communication_flows/escrow_chan_withdrawal.mmd +++ b/docs/communication_flows/escrow_chan_withdrawal.mmd @@ -5,58 +5,68 @@ sequenceDiagram actor HomeChain actor EscrowChain Note over HomeChain: User already has a home channel - Note over Node: Contains user's state with Home Chain + Note over Node: Contains user's state with Home Chain Note right of Client: Connected to Node + User->>Client: async withdraw(blockchainId, asset, amount) + %% Phase 1: Escrow Lock - lock funds for withdrawal Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns state Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, state.cycleId, state.version)) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) Note over Client: GetTokenAddress(blockchainId, asset) Note over Client: state.setEscrowToken(blockchainId, tokenAddress) Note over Client: GetEscrowChannelID(homeChannelDef, state.version) - Note over Client: NewTransition(escrowLockT, state.ID(), escrowChannelID, amount) + Note over Client: NewTransition(TransitionTypeEscrowLock, state.ID(), escrowChannelID, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreEscrowChannel(escrow_channel) - Note right of Node: StoreState(state) + Note over Node: LockUserState(userWallet, asset) + Note over Node: CheckOpenChannel(userWallet, asset) → approvedSigValidators + Note over Node: GetLastUserState(userWallet, asset) → currentState + Note over Node: EnsureNoOngoingStateTransitions(userWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState → verify userSig → nodeSigner.Sign(packedState) + Note over Node: StoreEscrowChannel(escrow_channel) + Note over Node: RecordTransaction → StoreUserState(state) Node->>Client: Return node signature + + %% Phase 2: On-chain escrow withdrawal initiation Note over Client: PackChannelDefinition(channelDef) Note over Client: PackState(channelId, state) Node->>EscrowChain: initiateEscrowWithdrawal(packedChannelDef, packedState) EscrowChain->>Node: Return Tx Hash EscrowChain-->>Node: Emits EscrowWithdrawalInitiated Event - Note right of Node: HandleEscrowWithdrawalInitiated() - Note right of Node: UpdateEscrowChannel(escrow_channel) + Note over Node: HandleEscrowWithdrawalInitiated() + Note over Node: escrowChannel.StateVersion = event.StateVersion + Note over Node: escrowChannel.Status = Open + Note over Node: UpdateChannel(escrowChannel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate - + + %% Phase 3: Escrow withdrawal finalization - debit home, credit escrow Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns state Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, state.cycleId, state.version)) - Note over Client: NewTransition(escrow_withdrawalT, state.ID(), escrowChannelID, amount) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) + Note over Client: NewTransition(TransitionTypeEscrowWithdraw, state.ID(), escrowChannelID, amount) Note over Client: state.applyTransitions(transitions) returns true Note over Client: signState(state) returns userSig Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreState(state) + Note over Node: LockUserState → CheckOpenChannel → GetLastUserState + Note over Node: ValidateStateAdvancement → PackState → verify sig → node sign + Note over Node: RecordTransaction → StoreUserState(state) Node->>Client: Return node signature + %% Phase 4: Finalize on-chain Note over Node: PackState(channelId, state) Client->>EscrowChain: finalizeEscrowWithdrawal(escrowChannelId, packedState) EscrowChain->>Client: Return Tx Hash EscrowChain-->>Node: Emits EscrowWithdrawalFinalized Event - Note right of Node: HandleEscrowWithdrawalFinalized() - Note right of Node: UpdateEscrowChannel(escrow_channel) + Note over Node: HandleEscrowWithdrawalFinalized() + Note over Node: escrowChannel.StateVersion = event.StateVersion + Note over Node: escrowChannel.Status = Closed + Note over Node: UpdateChannel(escrowChannel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate - \ No newline at end of file diff --git a/docs/communication_flows/home_chan_creation_from_scratch.mmd b/docs/communication_flows/home_chan_creation_from_scratch.mmd index 7b35cd279..76425d73e 100644 --- a/docs/communication_flows/home_chan_creation_from_scratch.mmd +++ b/docs/communication_flows/home_chan_creation_from_scratch.mmd @@ -8,20 +8,34 @@ sequenceDiagram Client->>Node: GetLastState(UserWallet, asset) Note right of Node: GetLastState(userWallet, asset) returns nil Node->>Client: Returns an "empty state with 0 version" error - Note over Client: newChannelDefinition() + Note over Client: newChannelDefinition(nonce, challenge, approvedSigValidators) Note over Client: newEmptyState(asset) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, cycleId, state.version)) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) Note over Client: GetTokenAddress(blockchainId, asset) Note over Client: state.setHomeToken(blockchainId, tokenAddress) - Note over Client: NewTransition(depositT, state.ID(), userWallet, amount) + Note over Client: NewTransition(TransitionTypeHomeDeposit, state.ID(), userWallet, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) + Client->>Node: RequestCreateChannel(channelDef, state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns nil - Note right of Node: ValidateChannelDefinition(channelDef) - Note right of Node: ValidateStateTransition(nil, state) - Note right of Node: StoreChannel(channel) - Note right of Node: StoreState(state) + Note over Node: Validate userWallet is valid hex address + Note over Node: IsAssetSupported(asset, tokenAddress, blockchainId) + Note over Node: SignerValidatorsSupported(approvedSigValidators) + Note over Node: LockUserState(userWallet, asset) + Note over Node: GetLastUserState(userWallet, asset) → nil → NewVoidState() + Note over Node: If channel exists and not final → reject "already initialized" + Note over Node: Calculate homeChannelID = GetHomeChannelID(node, user, asset, nonce, challenge, sigValidators) + Note over Node: Validate incoming homeChannelID matches calculated ID + Note over Node: Validate nonce != 0, challenge >= minChallenge + Note over Node: ValidateStateAdvancement(voidState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: Verify signer type approved: IsChannelSignerSupported(approvedSigValidators, sigType) + Note over Node: ChannelSigValidator.Verify(userWallet, packedState, userSig) + Note over Node: CreateChannel(homeChannelID, userWallet, asset, Home, blockchainID, token, nonce, challenge, approvedSigValidators) + Note over Node: nodeSigner.Sign(packedState) → nodeSig + Note over Node: NewTransactionFromTransition(state, transition) → RecordTransaction + Note over Node: StoreUserState(state) → also updates UserBalance Node->>Client: Return node signature Note over Client: GetChannelId(channelDef) Note over Client: PackChannelDefinition(channelDef) @@ -29,8 +43,9 @@ sequenceDiagram Client->>HomeChain: createHomeChannel(packedChannelDef, packedState) HomeChain->>Client: Return Tx Hash HomeChain-->>Node: Emits HomeChannelCreated Event - Note right of Node: HandleHomeChannelCreated() - Note right of Node: UpdateChannel(channel) + Note over Node: HandleHomeChannelCreated() + Note over Node: channel.StateVersion = event.StateVersion + Note over Node: channel.Status = Open + Note over Node: UpdateChannel(channel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate Client-->>User: Returns success - \ No newline at end of file diff --git a/docs/communication_flows/home_chan_deposit.mmd b/docs/communication_flows/home_chan_deposit.mmd index 02db0d350..30123727d 100644 --- a/docs/communication_flows/home_chan_deposit.mmd +++ b/docs/communication_flows/home_chan_deposit.mmd @@ -5,32 +5,40 @@ sequenceDiagram actor HomeChain Note over Client: Connected to Node Note over HomeChain: User already has a home channel - Note over Node: Contains user's state with Home Chain + Note over Node: Contains user's state with Home Chain User->>Client: deposit(blockchainId, asset, amount) Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns a state with home chain - %% Note over Client: Check current state transitions Note over Client: createNextState(currentState) returns state Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) - Note over Client: NewTransition(core.TransitionTypeHomeDeposit, state.ID(), userWallet, amount) + Note over Client: NewTransition(TransitionTypeHomeDeposit, state.ID(), userWallet, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreState(state) + Note over Node: ActionGateway.AllowAction(userWallet, GatedActionTransfer) + Note over Node: LockUserState(userWallet, asset) + Note over Node: CheckOpenChannel(userWallet, asset) → approvedSigValidators + Note over Node: GetLastUserState(userWallet, asset) → currentState + Note over Node: EnsureNoOngoingStateTransitions(userWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: Verify signer type approved: IsChannelSignerSupported(approvedSigValidators, sigType) + Note over Node: ChannelSigValidator.Verify(userWallet, packedState, userSig) + Note over Node: nodeSigner.Sign(packedState) → nodeSig + Note over Node: NewTransactionFromTransition(state, transition) → RecordTransaction + Note over Node: StoreUserState(state) → also updates UserBalance Node->>Client: Return node signature Note over Client: PackState(state) Client->>HomeChain: checkpoint(channelId, packedState) HomeChain->>Client: Return Tx Hash - HomeChain-->>Node: Emits Checkpointed Event - Note right of Node: HandleCheckpointed() - Note right of Node: UpdateChannel(channel) + HomeChain-->>Node: Emits HomeChannelCheckpointed Event + Note over Node: HandleHomeChannelCheckpointed() + Note over Node: channel.StateVersion = event.StateVersion + Note over Node: If challenged → set status = Open + Note over Node: UpdateChannel(channel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate Client-->>User: Returns success - \ No newline at end of file diff --git a/docs/communication_flows/home_chan_withdraw.mmd b/docs/communication_flows/home_chan_withdraw.mmd index 60ef5d801..f128fb6d7 100644 --- a/docs/communication_flows/home_chan_withdraw.mmd +++ b/docs/communication_flows/home_chan_withdraw.mmd @@ -5,32 +5,40 @@ sequenceDiagram actor HomeChain Note over Client: Connected to Node Note over HomeChain: User already has a home channel - Note over Node: Contains user's state with Home Chain + Note over Node: Contains user's state with Home Chain User->>Client: withdraw(blockchainId, asset, amount) Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns a state with home chain - %% Note over Client: Check current state transitions Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, cycleId, state.version)) - Note over Client: NewTransition(withdrawalT, state.ID(), userWallet, amount) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) + Note over Client: NewTransition(TransitionTypeHomeWithdrawal, state.ID(), userWallet, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) Client->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreState(state) + Note over Node: ActionGateway.AllowAction(userWallet, GatedActionTransfer) + Note over Node: LockUserState(userWallet, asset) + Note over Node: CheckOpenChannel(userWallet, asset) → approvedSigValidators + Note over Node: GetLastUserState(userWallet, asset) → currentState + Note over Node: EnsureNoOngoingStateTransitions(userWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: Verify signer type approved: IsChannelSignerSupported(approvedSigValidators, sigType) + Note over Node: ChannelSigValidator.Verify(userWallet, packedState, userSig) + Note over Node: nodeSigner.Sign(packedState) → nodeSig + Note over Node: NewTransactionFromTransition(state, transition) → RecordTransaction + Note over Node: StoreUserState(state) → also updates UserBalance Node->>Client: Return node signature Note over Client: PackState(channelId, state) Client->>HomeChain: checkpoint(channelId, packedState) HomeChain->>Client: Return Tx Hash - HomeChain-->>Node: Emits Checkpointed Event - Note right of Node: HandleCheckpointed() - Note right of Node: UpdateChannel(channel) + HomeChain-->>Node: Emits HomeChannelCheckpointed Event + Note over Node: HandleHomeChannelCheckpointed() + Note over Node: channel.StateVersion = event.StateVersion + Note over Node: If challenged → set status = Open + Note over Node: UpdateChannel(channel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate Client-->>User: Returns success - \ No newline at end of file diff --git a/docs/communication_flows/home_chan_withdraw_on_create_from_state.mmd b/docs/communication_flows/home_chan_withdraw_on_create_from_state.mmd index 992d0f983..93326d07e 100644 --- a/docs/communication_flows/home_chan_withdraw_on_create_from_state.mmd +++ b/docs/communication_flows/home_chan_withdraw_on_create_from_state.mmd @@ -7,23 +7,34 @@ sequenceDiagram Note over Node: Contains a state with no channel User->>Client: withdraw(blockchainId, asset, amount) Client->>Node: GetLastState(UserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>Client: Returns a state with no home chain - %% Note over Client: Check current state transitions - Note over Client: newChannelDefinition() + Note over Client: newChannelDefinition(nonce, challenge, approvedSigValidators) Note over Client: createNextState(currentState) returns state - Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, cycleId, state.version)) + Note over Client: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) Note over Client: GetTokenAddress(blockchainId, asset) Note over Client: state.setHomeToken(blockchainId, tokenAddress) - Note over Client: NewTransition(withdrawalT, state.ID(), userWallet, amount) + Note over Client: NewTransition(TransitionTypeHomeWithdrawal, state.ID(), userWallet, amount) Note over Client: state.applyTransitions(transitions) returns true - Note over Client: signState(state) returns userSig + Note over Client: signState(state) returns userSig (prepends signer type byte) + Client->>Node: RequestCreateChannel(channelDef, state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns pendingState - Note right of Node: ValidateChannelDefinition(channelDef) - Note right of Node: ValidateStateTransition(pendingState, state) - Note right of Node: StoreChannel(channel) - Note right of Node: StoreState(state) + Note over Node: Validate userWallet is valid hex address + Note over Node: IsAssetSupported(asset, tokenAddress, blockchainId) + Note over Node: SignerValidatorsSupported(approvedSigValidators) + Note over Node: LockUserState(userWallet, asset) + Note over Node: GetLastUserState(userWallet, asset) → pendingState + Note over Node: Calculate homeChannelID = GetHomeChannelID(node, user, asset, nonce, challenge, sigValidators) + Note over Node: Validate incoming homeChannelID matches calculated ID + Note over Node: Validate nonce != 0, challenge >= minChallenge + Note over Node: ValidateStateAdvancement(pendingState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: Verify signer type approved: IsChannelSignerSupported(approvedSigValidators, sigType) + Note over Node: ChannelSigValidator.Verify(userWallet, packedState, userSig) + Note over Node: CreateChannel(homeChannelID, userWallet, asset, Home, blockchainID, token, nonce, challenge, approvedSigValidators) + Note over Node: nodeSigner.Sign(packedState) → nodeSig + Note over Node: NewTransactionFromTransition(state, transition) → RecordTransaction + Note over Node: StoreUserState(state) → also updates UserBalance Node->>Client: Return node signature Note over Client: GetChannelId(channelDef) Note over Client: PackChannelDefinition(channelDef) @@ -31,8 +42,9 @@ sequenceDiagram Client->>HomeChain: createHomeChannel(packedChannelDef, packedState) HomeChain->>Client: Return Tx Hash HomeChain-->>Node: Emits HomeChannelCreated Event - Note right of Node: HandleHomeChannelCreated() - Note right of Node: UpdateChannel(channel) + Note over Node: HandleHomeChannelCreated() + Note over Node: channel.StateVersion = event.StateVersion + Note over Node: channel.Status = Open + Note over Node: UpdateChannel(channel) Node-->>Client: Sends ChannelUpdate & BalanceUpdate Client-->>User: Returns success - \ No newline at end of file diff --git a/docs/communication_flows/transfer.mmd b/docs/communication_flows/transfer.mmd index 4db410738..802cde6eb 100644 --- a/docs/communication_flows/transfer.mmd +++ b/docs/communication_flows/transfer.mmd @@ -3,35 +3,47 @@ sequenceDiagram actor SenderClient actor Node actor ReceiverClient - + Note over SenderClient: Connected to Node - Note over Node: Contains user's state with Home Chain + Note over Node: Contains user's state with Home Chain SenderUser->>SenderClient: transfer(DestinationUserWallet, asset, amount) SenderClient->>Node: GetLastState(SenderUserWallet, asset) - Note right of Node: GetLastState(userWallet, asset) Node->>SenderClient: Returns a state with home chain - %% Note over SenderClient: Check current state transitions Note over SenderClient: createNextState(currentState) returns state - Note over SenderClient: state.setID(CalculateStateID(state.userWallet, state.asset, cycleId, state.version)) - Note over SenderClient: NewTransition(transferT, state.ID(), DestinationSenderUserWallet, amount) + Note over SenderClient: state.setID(CalculateStateID(state.userWallet, state.asset, epoch, state.version)) + Note over SenderClient: NewTransition(TransitionTypeTransferSend, state.ID(), DestinationUserWallet, amount) Note over SenderClient: state.applyTransitions(transitions) returns true - Note over SenderClient: signState(state) returns userSig + Note over SenderClient: signState(state) returns userSig (prepends signer type byte) SenderClient->>Node: SubmitState(state, userSig) - Note right of Node: GetLastState(userWallet, asset) returns currentState - Note right of Node: EnsureNoOngoingTransitions() - Note right of Node: ValidateStateTransition(currentState, state) - Note right of Node: StoreState(state) - - Note right of Node: CreateReceiverState(DestinationUserWallet) - Note right of Node: GetLastState(DestinationUserWallet, asset) - %% Note over SenderClient: Check current state transitions - Note over Node: createNextState(receiver_state) returns new_receiver_state - Note over Node: new_receiver_state.setID(CalculateStateID(new_receiver_state.userWallet, new_receiver_state.asset, cycleId, new_receiver_state.version)) - Note over Node: NewTransition(transfer_receiveT, new_receiver_state.ID(), DestinationUserWallet, amount) - Note over Node: new_receiver_state.applyTransitions(transitions) returns true - Note over Node: signState(new_receiver_state) returns nodeSig + Note over Node: ActionGateway.AllowAction(userWallet, GatedActionTransfer) + Note over Node: LockUserState(senderWallet, asset) + Note over Node: CheckOpenChannel(senderWallet, asset) → approvedSigValidators + Note over Node: GetLastUserState(senderWallet, asset) → currentState + Note over Node: EnsureNoOngoingStateTransitions(senderWallet, asset) + Note over Node: ValidateStateAdvancement(currentState, incomingState) + Note over Node: PackState(incomingState) → packedState + Note over Node: Extract signer type from userSig (0x00=default, 0x01=session key) + Note over Node: Verify signer type approved: IsChannelSignerSupported(approvedSigValidators, sigType) + Note over Node: ChannelSigValidator.Verify(senderWallet, packedState, userSig) + Note over Node: nodeSigner.Sign(packedState) → senderNodeSig + + rect rgb(240, 248, 255) + Note over Node: issueTransferReceiverState() + Note over Node: Validate sender ≠ receiver + Note over Node: LockUserState(receiverWallet, asset) + Note over Node: GetLastUserState(receiverWallet, asset) → receiverState (or VoidState) + Note over Node: receiverState.NextState() → increment version + Note over Node: ApplyTransferReceiveTransition(senderWallet, amount, txID) + Note over Node: GetLastSignedState(receiverWallet, asset) + Note over Node: If last signed was MutualLock or EscrowLock → skip signing + Note over Node: Else if HomeChannelID exists → nodeSigner.Sign(packedReceiverState) + Note over Node: StoreUserState(receiverState) → also updates receiver UserBalance + end + + Note over Node: NewTransactionFromTransition(senderState, receiverState, transition) → RecordTransaction + Note over Node: StoreUserState(senderState) → also updates sender UserBalance Node->>SenderClient: Sends ChannelUpdate & BalanceUpdate Node->>ReceiverClient: Sends ChannelUpdate & BalanceUpdate diff --git a/docs/data_models.mmd b/docs/data_models.mmd index db5d2c9c8..9ce8ff680 100644 --- a/docs/data_models.mmd +++ b/docs/data_models.mmd @@ -1,151 +1,332 @@ classDiagram + %% ===== ENUMS ===== + class ChannelStatus { - open - closed - challenged + <> + 0 void + 1 open + 2 challenged + 3 closed } class ChannelType { - escrow - home + <> + 1 home + 2 escrow } - class Channel { - +string ChannelID // should be different for home and escrow channels - +string UserWallet // better than ParticipantWallet, because we won't have more than one participant - +ChannelType Type - +uint32 BlockchainID - +string Token - +uint64 Challenge - +uint64 Nonce - +ChannelStatus Status - +uint64 OnChainStateVersion - - +time.Time CreatedAt - +time.Time UpdatedAt + class TransactionType { + <> + 10 home_deposit + 11 home_withdrawal + 20 escrow_deposit + 21 escrow_withdraw + 30 transfer + 40 commit + 41 release + 42 rebalance + 100 migrate + 110 escrow_lock + 120 mutual_lock + 200 finalize } - class State { // Immutable - +[64]char ID // Deterministic: Hash(UserWallet, Asset, UserCycleIndex, Version) - - +string Data - +string Asset - +string UserWallet - +uint64 CycleIndex - - +uint64 Version + class TransitionType { + <> + 0 void + 1 acknowledgement + 10 home_deposit + 11 home_withdrawal + 20 escrow_deposit + 21 escrow_withdraw + 30 transfer_send + 31 transfer_receive + 40 commit + 41 release + 100 migrate + 110 escrow_lock + 120 mutual_lock + 200 finalize + } - +string *HomeChannelID - +string *EscrowChannelID + class AppSessionStatus { + <> + 0 void + 1 open + 2 closed + } - // It seem to better not to use State array in SC in CrossChainState - // And CrossChainState now can be renamed to "State" + class BlockchainActionType { + <> + 1 checkpoint + 10 initiate_escrow_deposit + 11 finalize_escrow_deposit + 20 initiate_escrow_withdrawal + 21 finalize_escrow_withdrawal + } - // Home Channel - +int256 HomeUserBalance - +int64 HomeUserNetFlow - +int256 HomeNodeBalance - +int64 HomeNodeNetFlow + class BlockchainActionStatus { + <> + 0 pending + 1 completed + 2 failed + } - // Escrow Channel - +int256 EscrowUserBalance - +int64 EscrowUserNetFlow - +int256 EscrowNodeBalance - +int64 EscrowNodeNetFlow + class GatedAction { + <> + 1 transfer + 10 app_session_creation + 11 app_session_operation + 12 app_session_deposit + 13 app_session_withdrawal + } - +bool IsFinal - %% TODO: Remove in the future if redundant + %% ===== CORE TABLES ===== - +string UserSig - +string NodeSig + class Channel { + +char~66~ channel_id PK + +char~42~ user_wallet + +varchar~20~ asset + +smallint type + +numeric blockchain_id + +char~42~ token + +bigint challenge_duration + +timestamptz challenge_expires_at + +numeric nonce + +varchar~66~ approved_sig_validators + +smallint status + +numeric state_version + +timestamptz created_at + +timestamptz updated_at + } - +time.Time CreatedAt + class ChannelState { + +char~66~ id PK + +varchar~20~ asset + +char~42~ user_wallet + +numeric epoch + +numeric version + +smallint transition_type + +char~66~ transition_tx_id FK + +varchar~66~ transition_account_id + +numeric transition_amount + +char~66~ home_channel_id FK + +char~66~ escrow_channel_id FK + +numeric home_user_balance + +numeric home_user_net_flow + +numeric home_node_balance + +numeric home_node_net_flow + +numeric escrow_user_balance + +numeric escrow_user_net_flow + +numeric escrow_node_balance + +numeric escrow_node_net_flow + +text user_sig + +text node_sig + +timestamptz created_at } - class ChannelOfTypeHome { + class Transaction { + +char~66~ id PK + +smallint tx_type + +varchar~20~ asset_symbol + +varchar~66~ from_account + +varchar~66~ to_account + +char~66~ sender_new_state_id FK + +char~66~ receiver_new_state_id FK + +numeric amount + +timestamptz created_at } - class ChannelOfTypeEscrow { + class UserBalance { + +char~42~ user_wallet PK + +varchar~20~ asset PK + +numeric balance + +timestamptz created_at + +timestamptz updated_at } - %% Relationships - Channel --> ChannelStatus - State --> ChannelOfTypeHome - State --> ChannelOfTypeEscrow - ChannelOfTypeHome --> Channel - ChannelOfTypeEscrow --> Channel - Channel --> ChannelType - Transaction --> TransactionType - Transaction --> SenderNewState - Transaction --> ReceiverNewState - SenderNewState --> State - ReceiverNewState --> State + %% ===== APPLICATION TABLES ===== - class SenderNewState { - *Optional + class AppV1 { + +varchar~66~ id PK + +char~42~ owner_wallet + +text metadata + +numeric version + +boolean creation_approval_not_required + +timestamptz created_at + +timestamptz updated_at } - class ReceiverNewState { - *Optional + class AppSessionV1 { + +char~66~ id PK + +varchar~66~ application_id FK + +numeric nonce + +text session_data + +smallint quorum + +numeric version + +smallint status + +timestamptz created_at + +timestamptz updated_at } + class AppParticipantV1 { + +char~66~ app_session_id PK FK + +char~42~ wallet_address PK + +smallint signature_weight + } - class Transaction { // Immutable - +[64]char ID - // Deterministic: - // 1) Initiated by User: Hash(ToAccount, SenderNewStateID) - // 2) Initiated by Node: Hash(FromAccount, ReceiverNewStateID) - - +TransactionType Type - +string AssetSymbol - - +string FromAccount - +string ToAccount + class AppLedgerEntryV1 { + +uuid id PK + +char~66~ account_id FK + +varchar~20~ asset_symbol + +char~42~ wallet + +numeric credit + +numeric debit + +timestamptz created_at + } - +[64]char *SenderNewStateID - +[64]char *ReceiverNewStateID + %% ===== SESSION KEY TABLES ===== + + class AppSessionKeyStateV1 { + +char~66~ id PK + +char~42~ user_address + +char~42~ session_key + +numeric version + +timestamptz expires_at + +text user_sig + +timestamptz created_at + +timestamptz updated_at + } - +decimal.Decimal Amount - +time.Time CreatedAt + class AppSessionKeyApplicationV1 { + +char~66~ session_key_state_id PK FK + +varchar~66~ application_id PK FK } - class TransactionType { - %% isWallet(ToAccount) && isWallet(FromAccount) -> transfer - transfer + class AppSessionKeyAppSessionV1 { + +char~66~ session_key_state_id PK FK + +char~66~ app_session_id PK FK + } - %% ToAccount = AppSessionID -> commit - %% FromAccount = UserWallet -> commit - commit + class ChannelSessionKeyStateV1 { + +char~66~ id PK + +char~42~ user_address + +char~42~ session_key + +numeric version + +char~66~ metadata_hash + +timestamptz expires_at + +text user_sig + +timestamptz created_at + } - %% ToAccount = UserWallet -> release - %% FromAccount = AppSessionID -> release - release + class ChannelSessionKeyAssetV1 { + +char~66~ session_key_state_id PK FK + +varchar~20~ asset PK + } - %% FromAccount = HomeChannelID -> home_deposit - %% ToAccount = UserWallet -> home_deposit - home_deposit + %% ===== BLOCKCHAIN TABLES ===== + + class ContractEvent { + +bigserial id PK + +char~42~ contract_address + +numeric blockchain_id + +varchar~255~ name + +numeric block_number + +varchar~255~ transaction_hash + +bigint log_index + +timestamptz created_at + } - %% FromAccount = UserWallet -> home_withdrawal - %% ToAccount = HomeChannelID -> home_withdrawal - home_withdrawal + class BlockchainAction { + +bigserial id PK + +smallint action_type + +char~66~ state_id FK + +numeric blockchain_id + +jsonb action_data + +smallint status + +smallint retry_count + +text last_error + +char~66~ transaction_hash + +timestamptz created_at + +timestamptz updated_at + } - %% ToAccount = EscrowChannelID -> mutual_lock - %% FromAccount = HomeChannelID -> mutual_lock - mutual_lock + %% ===== OPERATIONAL TABLES ===== - %% FromAccount = EscrowChannelID -> escrow_deposit - %% ToAccount = HomeChannelID -> escrow_deposit - escrow_deposit - - %% ToAccount = HomeChannelID -> escrow_lock - %% FromAccount = EscrowChannelID -> escrow_lock - escrow_lock + class UserStakedV1 { + +char~42~ user_wallet PK + +numeric blockchain_id PK + +numeric amount + +timestamptz created_at + +timestamptz updated_at + } - %% FromAccount = HomeChannelID -> escrow_withdraw - %% ToAccount = EscrowChannelID -> escrow_withdraw - escrow_withdraw + class ActionLogEntryV1 { + +uuid id PK + +char~42~ user_wallet + +smallint gated_action + +timestamptz created_at + } - %% FromAccount = HomeChannelID -> migrate - %% ToAccount = EscrowChannelID -> migrate - migrate + class LifespanMetric { + +varchar~66~ id PK + +varchar~255~ name + +jsonb labels + +numeric value + +timestamptz last_timestamp + +timestamptz updated_at } + + %% ===== RELATIONSHIPS ===== + + %% -- Channel core -- + Channel --> ChannelStatus : status + Channel --> ChannelType : type + + %% -- ChannelState references channels and transitions -- + ChannelState --> TransitionType : transition_type + ChannelState --> Channel : home_channel_id + ChannelState --> Channel : escrow_channel_id + ChannelState --> Transaction : transition_tx_id + + %% -- Transaction references states -- + Transaction --> TransactionType : tx_type + Transaction --> ChannelState : sender_new_state_id + Transaction --> ChannelState : receiver_new_state_id + + %% -- UserBalance derived from ChannelState.home_user_balance -- + ChannelState ..> UserBalance : StoreUserState updates balance + + %% -- Blockchain: actions reference states, events update channels -- + BlockchainAction --> BlockchainActionType : action_type + BlockchainAction --> BlockchainActionStatus : status + BlockchainAction --> ChannelState : state_id + ContractEvent ..> Channel : events update channel status + ContractEvent ..> BlockchainAction : events schedule actions + ContractEvent ..> UserStakedV1 : UserLockedBalanceUpdated + + %% -- App layer -- + AppSessionV1 --> AppV1 : application_id + AppSessionV1 --> AppSessionStatus : status + AppParticipantV1 --> AppSessionV1 : app_session_id + AppLedgerEntryV1 --> AppSessionV1 : account_id + + %% -- App session keys link to apps and sessions -- + AppSessionKeyApplicationV1 --> AppSessionKeyStateV1 : session_key_state_id + AppSessionKeyApplicationV1 --> AppV1 : application_id + AppSessionKeyAppSessionV1 --> AppSessionKeyStateV1 : session_key_state_id + AppSessionKeyAppSessionV1 --> AppSessionV1 : app_session_id + + %% -- Channel session keys link to channels via user_address + asset -- + ChannelSessionKeyAssetV1 --> ChannelSessionKeyStateV1 : session_key_state_id + ChannelSessionKeyStateV1 ..> Channel : validated against user_address + asset + + %% -- Action log gates user operations -- + ActionLogEntryV1 --> GatedAction : gated_action + ActionLogEntryV1 ..> AppSessionV1 : gates session operations + + %% -- Lifespan metrics aggregate from core tables -- + LifespanMetric ..> Channel : aggregates channel counts + LifespanMetric ..> Transaction : aggregates TVL + LifespanMetric ..> AppSessionV1 : aggregates session counts + LifespanMetric ..> UserBalance : counts active users diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 000000000..e3fa489b4 --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,402 @@ +# Nitrolite Documentation Guide + +This document defines **how documentation should be written and structured** inside the Nitrolite repository. + +Its purpose is to ensure that documentation: + +* is consistent across the repository +* is easy for developers to navigate +* is easily retrievable by AI systems +* avoids duplication +* focuses on the most important information first + +This guide must be followed when writing any documentation for the Nitrolite repository. + +--- + +# 1. Documentation Principles + +All documentation in the Nitrolite repository must follow these principles. + +## 1.1 Single Source of Truth + +Every piece of information must exist in **one canonical location**. + +Other places may reference it but must not duplicate the content. + +Example: + +| Information | Canonical location | +| --------------------- | ------------------------------ | +| Protocol definitions | `docs/protocol/terminology.md` | +| System architecture | `docs/architecture/` | +| Build Apps on Yellow Network | `docs/build/` | +| Operator instructions | `docs/operator/` | +| Code behaviour | Go code comments | + +The website documentation repository must **reuse content from the main repository**, not redefine it. + +--- + +## 1.2 AI-Friendly Documentation + +Documentation should be written in a way that allows AI systems to reliably retrieve answers. + +This requires: + +* clear headings +* explicit terminology +* short conceptual sections +* clear definitions +* structured documents + +Avoid narrative writing or long unstructured explanations. + +--- + +## 1.3 Clear Separation of Concerns + +Documentation must be separated into four main domains: + +1. Protocol +2. System Architecture +3. Build (as in "build your applications on Yellow Network") +4. Operator + +Each domain serves a different audience and must not mix responsibilities. + +--- + +# 2. Documentation Structure + +All documentation inside the repository must follow this directory structure. + +``` +docs/ + protocol/ + architecture/ + build/ + operator/ +``` + +Each directory contains documentation for a specific domain. + +--- + +# 3. Terminology Documentation + +Terminology must be defined in a single canonical document. + +Location: + +``` +docs/protocol/terminology.md +``` + +This document defines all protocol-level concepts. + +Examples of concepts that belong here include: + +* Channel +* State +* Epoch +* Settlement +* Operator +* Client + +Each term must be defined once and used consistently across all documentation. + +--- + +## Terminology Format + +Each term must follow the same structure. + +Example: + +``` +## Channel + +Definition +A channel is a state container shared between participants that allows +off-chain updates while maintaining on-chain security guarantees. + +Purpose +Channels enable fast off-chain execution while preserving the ability +to settle on-chain if necessary. + +Used In +- Channel lifecycle +- State updates +- Settlement +``` + +Terminology definitions must not contain implementation details. + +--- + +# 4. Protocol Documentation + +Protocol documentation describes **the system as a protocol**, independent of any specific implementation. + +A reader must be able to implement the protocol from this documentation without reading the Nitrolite code. + +Location: + +``` +docs/protocol/ +``` + +Recommended documents: + +``` +overview.md +terminology.md +state-advancement.md +state-enforcement.md +``` + +--- + +## Protocol Documentation Must Include + +Protocol documents must describe: + +* protocol concepts +* state structures +* rules governing state transitions +* lifecycle of channels +* settlement and dispute behaviour +* interaction with blockchains + +Protocol documentation must avoid: + +* code references +* repository structure +* implementation details + +## Language for Structures and Functions + +Protocol documentation must use **language-neutral pseudocode** when describing structures or functions. + +Use simple struct-like notation for data structures. + +Use plain function signatures with named parameters and return types. + +Rules: + +* Do not use syntax specific to any programming language (Go, TypeScript, Solidity, etc.) +* Use CamelCase for field and function names +* Keep pseudocode minimal — only show what is needed to convey the concept + +--- + +# 5. System Architecture Documentation + +System architecture documentation explains **how the Nitrolite implementation realizes the protocol**. + +Location: + +``` +docs/architecture/ +``` + +Recommended documents: + +``` +system-overview.md +node-architecture.md +storage.md +networking.md +security.md +``` + +--- + +## Architecture Documentation Must Include + +Architecture documentation must describe: + +* system components +* internal services +* communication patterns +* storage model +* security mechanisms +* how the protocol is implemented + +Architecture documentation may reference code modules. + +Architecture documentation must not redefine protocol rules. + +--- + +# 6. Separating Protocol and Architecture + +The protocol and architecture documentation may appear similar because the protocol was developed together with the implementation. + +However they must remain conceptually separate. + +### Protocol answers + +``` +What are the rules of the system? +``` + +### Architecture answers + +``` +How does Nitrolite implement those rules? +``` + +Example: + +| Topic | Protocol | Architecture | +| ----------------- | --------------------------------- | -------------------------------------------- | +| State | Defines state structure and rules | Explains how state is stored | +| Settlement | Defines settlement process | Explains which component executes settlement | + +Protocol documentation must remain **implementation-independent**. + +Architecture documentation describes **Clearnet specifically**. + +--- + +# 7. "Build Apps on Yellow Network" Documentation + +Build documentation must onboard developers to start building on top of Yellow Network with minimum friction. This documentation must highlight only protocol concepts and SDK methods necessary for app developers. It must not describe protocol internals. + + +Location: + +``` +docs/build/ +``` + +Recommended documents: + +``` +overview.md +app.md +develop.md +examples.md +``` + + + +1. **app.md** must cover: + +* how to register an app +* app session lifecycle +* concept of daily allowances +* app session keys + +2. **develop.md** must list SDK methods necessary for app development. +3. **examples.md** must show real-world use case examples of application flows built with the SDK. Starting with simplest examples and gradually increasing complexity. + + +--- + +# 8. Operator Documentation + +Operator documentation explains how to run and maintain infrastructure. + +Location: + +``` +docs/operator/ +``` + +Recommended documents: + +``` +running-node.md +configuration.md +monitoring.md +upgrades.md +``` + +--- + +## Operator Documentation Must Include + +Operator documentation must cover: + +* node deployment +* configuration parameters +* operational procedures +* monitoring requirements +* upgrade procedures + +Operator documentation must not include protocol explanations. + +--- + +# 9. Document Structure Requirements + +All documents must follow a predictable structure. + +This ensures both developers and AI systems can quickly locate information. + +--- + +## README.md Structure + +Every repository README must contain the following Header, followed with flexible component-specific sections. + +``` +# Project Name + +Short description of the project. + +## Overview + +High level explanation of the system. + +## Documentation + +Links to detailed documentation. +``` + +--- + +## Overview Document Structure + +Overview documents must contain: + +``` +# Overview + +## Purpose + +Why this component exists. + +## Concepts + +Key ideas required to understand it. + +## How It Works + +Explanation of behaviour and interactions. + +## Table of contents + +Bulletpoints with links to documentation and short descriptions. +``` + +--- + +# 10. Writing Requirements + +All documentation must follow these writing rules. + +### Use precise terminology + +Always use defined protocol terms. + +### Avoid ambiguity + +Explain behaviour explicitly. + +### Avoid implementation leakage in protocol docs + +Protocol documentation must not reference code. diff --git a/docs/protocol/channel-protocol.md b/docs/protocol/channel-protocol.md new file mode 100644 index 000000000..803b94a13 --- /dev/null +++ b/docs/protocol/channel-protocol.md @@ -0,0 +1,272 @@ +# Channel Protocol + +Previous: [State Model](state-model.md) | Next: [Enforcement and Settlement](enforcement.md) + +--- + +This document describes how channels operate and how states evolve through off-chain state advancement. + +## Purpose + +Channels are the primary mechanism for off-chain interaction in the Nitrolite protocol. They allow participants to exchange assets and update state without on-chain transactions. + +## Channel Definition + +A channel is defined by a set of immutable parameters fixed at creation time. + +| Field | Description | +| --------------------------- | -------------------------------------------------------------- | +| User | Identifier of the user participant | +| Node | Identifier of the node participant | +| Asset | Identifier of the asset operated within the channel | +| Nonce | Unique nonce to distinguish channels with identical parameters | +| ChallengeDuration | Challenge period duration in seconds | +| ApprovedSignatureValidators | Bitmask of approved signature validation modes | + +The channel definition MUST NOT change after creation. + +## Channel Identifier + +The channel identifier is derived deterministically from the channel definition using canonical encoding and hashing. + +The derivation produces a 32-byte identifier where: + +- the first byte encodes the smart contract version +- the remaining bytes are derived from the hash of the canonical encoded channel definition parameters + +This ensures that: + +- each unique channel definition produces a unique identifier +- the identifier can be independently computed by any party +- no central authority is required to assign identifiers +- identifiers are scoped to a specific protocol version + +## Channel Lifecycle + +A channel progresses through four primary actions. + +**Create** *(off-chain, then optionally on-chain)* +The node validates and stores the channel definition. An initial state is constructed and signed by all participants. This initial state MAY subsequently be submitted to the blockchain layer for on-chain enforcement, or any later state with a higher version MAY be used instead. + +**Checkpoint** *(off-chain, then optionally on-chain)* +The node validates and stores a new state off-chain. Depending on the transition type or a participant's initiative, the node MAY also submit the state to the blockchain layer for on-chain enforcement. Any party MAY independently submit a signed state to the blockchain layer. + +**Challenge** *(on-chain only)* +A participant submits a signed state along with a challenger signature to the blockchain layer. Upon successful validation, the challenge duration begins. During this period, other participants MAY respond by submitting a state with a higher version (if exists) via checkpoint to refute the challenge. + +**Close** *(off-chain for cooperative, on-chain for execution)* +Off-chain, a close represents a mutual agreement to finalize the channel. On-chain, a close MAY be executed either through a mutually signed close state or after the challenge duration has elapsed without a successful response. Upon close, the channel's funds are released according to the final state allocations and the channel's lifecycle ends. + +## State Signing Categories + +During the channel lifecycle, states exist in one of the following signing categories: + +**Mutually signed state** — a state that carries valid signatures from both the user and the node. This is the authoritative off-chain state and the only category that is enforceable on-chain. + +**Node-issued pending state** — a state produced by the node (e.g. for TransferReceive or Release transitions) that carries only the node's signature. A pending state is not enforceable on-chain and MUST NOT be treated as the latest authoritative state. It becomes mutually signed only after the user acknowledges it. + +The off-chain and enforcement representations encode the same logical state. A state that is mutually signed off-chain is directly enforceable on-chain without transformation, provided the enforcement representation is derived correctly. Session-key signatures are valid for enforcement if the channel's approved signature validators include the session key validator. + +## State Advancement Rules + +When a new state is proposed during off-chain advancement, the following general rules apply: + +**Version validation** +The state version MUST equal the current version incremented by one. + +**Signature validation** +A valid signature from the proposing participant MUST be present. The signature validation mode MUST be among the channel's approved signature validators. + +**Channel binding** +The channel identifier MUST be present and MUST match the channel definition. + +**Transition admissibility** +The transition type MUST be valid for the current channel state. Transition-specific validation rules MUST be satisfied. + +**Ledger admissibility** +Ledger invariants MUST hold: allocations MUST equal net flows, and allocation values MUST be non-negative. Declared decimal precision MUST match the asset's actual precision. Additionally, transition-specific ledger validations apply. + +## Transition Families + +Transitions are organized into the following families: + +**Local channel transitions** — operations that affect the channel's home ledger directly: Home Deposit, Home Withdrawal, Finalize. + +**Transfer transitions** — operations that move assets between users via the node: TransferSend, TransferReceive, Acknowledgement. + +**Extension bridge transitions** — operations that move assets between the channel and an extension: Commit, Release. + +**Cross-chain escrow transitions** — operations that manage cross-chain deposits and withdrawals through escrow: Escrow Deposit Initiate, Escrow Deposit Finalize, Escrow Withdrawal Initiate, Escrow Withdrawal Finalize. + +**Migration transitions** — operations that move the channel's home chain: Migration Initiate, Migration Finalize. + +## Transitions + +Each transition below describes its purpose, the expected transition field values, and the resulting ledger effects. Ledger fields are abbreviated as: UB (UserAllocation), UNF (UserNetFlow), NB (NodeAllocation), NNF (NodeNetFlow). + +For all transitions that do not modify the non-home ledger, the non-home ledger MUST be empty (see [Empty Non-Home Ledger](state-model.md#empty-non-home-ledger)). + +State Ledgers Operation-specific advancement diagram: + +![State Ledger Advancement](./state_ledger_advancement.png) + +### Acknowledgement + +- Purpose: allows the user to acknowledge and sign a pending node-issued state +- Acknowledgement creates a new state version. The new state is identical to the pending node-issued state in all fields except version (incremented by one) and the addition of the user's signature +- Valid only when the current state has no user signature +- Applies only to node-issued pending states (TransferReceive, Release) +- A node-issued pending state is NOT enforceable on-chain before acknowledgement, because it lacks the user's signature +- The non-home ledger MUST be empty + +### Home Deposit + +- Purpose: records an asset deposit from the home chain into the channel +- AccountId MUST reference the home channel identifier +- Amount MUST be the deposited quantity +- Home ledger effects: UB increases by Amount, UNF increases by Amount +- The non-home ledger MUST be empty +- Requires an on-chain checkpoint to lock the deposited assets + +### Home Withdrawal + +- Purpose: records an asset withdrawal from the channel to the home chain +- AccountId MUST reference the home channel identifier +- Amount MUST be the withdrawn quantity +- Home ledger effects: UB decreases by Amount, UNF decreases by Amount +- The non-home ledger MUST be empty +- Requires an on-chain checkpoint to release the withdrawn assets + +### TransferSend + +- Purpose: transfers assets from the user to a counterparty via the node +- AccountId MUST reference the receiver's address +- Amount MUST be the transfer quantity +- TxId uniquely identifies this transfer and is used to correlate with the corresponding TransferReceive on the receiver's channel +- Home ledger effects: UB decreases by Amount, NNF decreases by Amount +- The non-home ledger MUST be empty + +### TransferReceive + +- Purpose: records an inbound transfer from a counterparty via the node +- AccountId MUST reference the sender's address +- Amount MUST exactly match the sender's TransferSend amount (no scaling or normalization; transfers require the same unified asset) +- TxId MUST match the TxId from the corresponding TransferSend +- Home ledger effects: UB increases by Amount, NNF increases by Amount +- The non-home ledger MUST be empty +- This is a node-issued pending state: it carries only the node's signature and MUST NOT be considered the last mutually signed state until the user acknowledges it + +### Commit + +- Purpose: moves assets from the channel into an extension (such as an application session) +- AccountId MUST reference the extension object identifier (e.g. application session id) +- Amount MUST be the committed quantity +- Home ledger effects: UB decreases by Amount, NNF decreases by Amount +- The non-home ledger MUST be empty + +### Release + +- Purpose: returns assets from an extension back to channel allocations +- AccountId MUST reference the extension object identifier (e.g. application session id) +- Amount MUST be the released quantity +- The extension state MUST authorize the release +- Home ledger effects: UB increases by Amount, NNF increases by Amount +- The non-home ledger MUST be empty +- This is a node-issued pending state: it carries only the node's signature and MUST NOT be considered the last mutually signed state until the user acknowledges it + +### Escrow Deposit Initiate + +- Purpose: initiates a cross-chain deposit by creating an escrow between the home and non-home chains +- AccountId MUST reference the escrow channel identifier (derived from the home channel identifier and state version) +- Amount MUST be the deposit quantity +- A non-home ledger MUST be provided in the state +- The non-home ledger MUST have a different blockchain identifier than the home ledger +- Home ledger effects: NB increases by Amount, NNF increases by Amount +- Non-home ledger is initialized: UB set to Amount, UNF set to Amount, NB and NNF set to zero + +### Escrow Deposit Finalize + +- Purpose: completes a cross-chain deposit previously initiated by an escrow deposit initiate +- AccountId MUST reference the escrow channel identifier +- Amount MUST match the amount from the initiating transition +- Home ledger effects: UB increases by Amount, NB decreases by Amount, NNF does not change +- Non-home ledger effects: UB decreases by Amount, NNF decreases by Amount + +### Escrow Withdrawal Initiate + +- Purpose: initiates a cross-chain withdrawal by creating an escrow on the non-home chain +- AccountId MUST reference the escrow channel identifier (derived from the home channel identifier and state version) +- Amount MUST be the withdrawal quantity +- A non-home ledger MUST be provided in the state +- The non-home ledger MUST have a different blockchain identifier than the home ledger +- Non-home ledger is initialized: NB set to Amount, NNF set to Amount, UB and UNF set to zero + +### Escrow Withdrawal Finalize + +- Purpose: completes a cross-chain withdrawal previously initiated by an escrow withdrawal initiate +- AccountId MUST reference the escrow channel identifier +- Amount MUST match the amount from the initiating transition +- Home ledger effects: UB decreases by Amount, NNF decreases by Amount +- Non-home ledger effects: UNF decreases by Amount, NB decreases by Amount + +### Migration Initiate + +- Purpose: initiates migration of the channel from the current home chain to a different chain +- AccountId MUST reference the escrow channel identifier +- A non-home ledger MUST be provided in the state +- On the home chain (outgoing): UB MUST remain unchanged, UNF MUST NOT change, NB MUST be zero; the non-home ledger NB MUST equal the home ledger UB (normalized by decimal precision), non-home NNF MUST equal non-home NB, non-home UB and UNF MUST be zero +- On the non-home chain (incoming): the blockchain layer internally swaps ledgers so the non-home ledger becomes the home ledger; NB MUST equal the user allocation from the originating chain (normalized by decimal precision), NNF MUST equal NB, UB MUST be zero, UNF MUST be zero; the node locks funds equal to NB + +VERSION NOTE: Migration transitions are functional but may be refined in future protocol versions. + +### Migration Finalize + +- Purpose: completes a previously initiated migration +- The version MUST be the immediate successor of the migration initiate state +- On the new home chain: UB MUST equal the user allocation from the initiate state, NB MUST be zero, UNF and NNF MUST NOT change from the initiate state; the non-home ledger MUST be zeroed out; the channel transitions to operating status; no fund movement occurs +- On the old home chain: the blockchain layer internally swaps ledgers before validation; UB and NB on the old home MUST be zero; the non-home ledger carries the user allocation to the new chain; all locked funds are released and the channel is marked as migrated out + +VERSION NOTE: Migration transitions are functional but may be refined in future protocol versions. + +### Finalize + +- Purpose: indicates cooperative intent to close the channel and release all funds +- AccountId MUST reference the home channel identifier +- Amount MUST equal the user's current UB +- Home ledger effects: UNF decreases by the current UB, UB is set to zero +- The non-home ledger MUST be empty — open escrows or incomplete migrations MUST be resolved before finalization +- All participants MUST sign +- Final allocations become the settlement distribution +- NodeAllocation on finalization reflects the node's remaining share + +## Atomicity and Dependent State Changes + +Certain transitions produce side effects that create or modify states in other channels. The entire advancement — including all dependent state changes — MUST succeed or fail as a whole. + +**TransferSend** — when the node accepts a TransferSend, it MUST atomically create the corresponding TransferReceive state on the receiver's channel. If receiver-side state creation fails, the sender-side advancement MUST also fail. + +**Release** — when an extension releases assets, the node MUST atomically create the Release state on the user's channel. + +**Cross-chain escrow transitions** — escrow initiate and finalize operations MAY trigger on-chain actions (escrow creation, fund locking) that MUST be coordinated with the off-chain state change. + +## Checkpoint-Relevant Transitions + +The following transitions require or MAY trigger a checkpoint to the blockchain layer. These are all transitions whose intent does not map to OPERATE: + +| Transition | Intent | Checkpoint Behaviour | +| -------------------------- | -------------------------- | ------------------------------------------- | +| Home Deposit | DEPOSIT | Required to lock deposited assets | +| Home Withdrawal | WITHDRAW | Required to release withdrawn assets | +| Escrow Deposit Initiate | INITIATE_ESCROW_DEPOSIT | Required to create escrow on non-home chain | +| Escrow Deposit Finalize | FINALIZE_ESCROW_DEPOSIT | Required to complete cross-chain deposit | +| Escrow Withdrawal Initiate | INITIATE_ESCROW_WITHDRAWAL | Required to create escrow for withdrawal | +| Escrow Withdrawal Finalize | FINALIZE_ESCROW_WITHDRAWAL | Required to release assets on non-home chain| +| Migration Initiate | INITIATE_MIGRATION | Required to begin chain migration | +| Migration Finalize | FINALIZE_MIGRATION | Required to complete chain migration | +| Finalize | CLOSE | Required to settle and release funds | + +Any transition MAY also be checkpointed at a participant's discretion to enforce the current state on-chain. Any party MAY independently submit a validly signed state to the blockchain layer. + +--- + +Previous: [State Model](state-model.md) | Next: [Enforcement and Settlement](enforcement.md) diff --git a/docs/protocol/cross-chain-and-assets.md b/docs/protocol/cross-chain-and-assets.md new file mode 100644 index 000000000..91e0c95ed --- /dev/null +++ b/docs/protocol/cross-chain-and-assets.md @@ -0,0 +1,151 @@ +# Cross-Chain and Asset Model + +Previous: [Enforcement and Settlement](enforcement.md) | Next: [Interactions](interactions.md) + +--- + +This document describes the unified asset model and cross-chain functionality. + +## Purpose + +The unified asset model allows participants to operate on assets from multiple blockchains within a single channel. This eliminates the need for separate channels per blockchain and enables cross-chain interactions. + +## Unified Asset Concept + +Assets in the Nitrolite protocol are identified independently of any specific blockchain. + +A unified asset is defined by: + +| Field | Description | +| -------- | -------------------------------------------------- | +| Symbol | Human-readable canonical asset identifier (e.g. "USDC") | +| Decimals | Decimal precision of the asset | + +### Canonical Asset Identification + +The protocol identifies a unified asset by its symbol. Within channel metadata, the symbol is represented as the first 8 bytes of its Keccak-256 hash, providing a compact canonical identifier. Two chain-specific tokens are recognized as the same unified asset if they share the same symbol-derived identifier and are configured as such by the node. + +Symbol collisions are prevented by the node's asset configuration. The protocol does not maintain a global on-chain registry of unified assets. + +### Amount Normalization + +Assets on different blockchains MAY have different decimal precisions (e.g. USDC has 6 decimals on Ethereum but may have different precision on other chains). The protocol normalizes amounts for cross-chain comparisons using WAD normalization, which scales chain-specific amounts as if a token had 18 decimals: + +``` +NormalizedAmount = Amount * 10^(18 - ChainDecimals) +``` + +Each unified asset defines a canonical decimal precision (e.g. 6 for USDC) that is used during User <> Clearnode interactions (e.g. on-chain deposit, on-chain state submission requests, transfers, app session operations etc.). + +Rules: + +- Normalization is used **only for cross-chain comparisons** (e.g. validating that escrow amounts match across chains). It is not used for storage or accounting — stored values remain in their chain-native precision. +- The asset's configured decimal precision acts as the base, whereas 18 is the target of the upscaling. The maximum supported decimal precision is 18. +- Normalization is exact and lossless when scaling up. No rounding or remainder occurs. +- The blockchain layer validates that declared decimals match the actual token decimals on the current chain. + +## Home Chain + +The home chain is the blockchain against which a given channel state is enforced. It is identified by the chain identifier in the home ledger of that state. + +The home chain determines: + +- where enforcement operations for that state are executed +- which blockchain holds the locked funds for the channel +- the authoritative source for state validation + +The home chain MAY change over the lifetime of a channel through a migration operation. After migration, the new home chain becomes the authoritative enforcement target. + +## Home and Non-Home Ledger Roles + +**Home Ledger** +The home ledger is the primary record of asset allocations. It is associated with the home chain and is directly enforceable through the blockchain layer. + +Responsibilities: + +- tracks the authoritative asset allocations +- receives checkpoints for enforcement +- holds deposited assets in the enforcement contract + +**Non-Home Ledger** +The non-home ledger tracks asset allocations on a blockchain other than the home chain. When no cross-chain operation is in progress, the non-home ledger MUST be empty (see [Empty Non-Home Ledger](state-model.md#empty-non-home-ledger)). + +Responsibilities: + +- tracks assets involved in cross-chain escrow operations +- reflects cross-chain deposit and withdrawal allocations +- coordinates with the home ledger for consistency + +## Escrow Model + +Cross-chain operations use an **escrow** mechanism to coordinate fund movements across two independent blockchains. + +An escrow is a temporary on-chain record that locks funds on one chain while a corresponding state update is being finalized on the other chain. Each escrow is identified by an **escrow channel identifier**, derived deterministically from the home channel identifier and the state version at initiation. + +| Property | Description | +| -------------- | --------------------------------------------------------------- | +| Identifier | 32-byte hash derived from the home channel identifier and state version | +| Hosting chain | The non-home chain (for deposits: where the user's funds are locked; for withdrawals: where the node's funds are locked) | +| Tracked amount | The amount locked in escrow, corresponding to the non-home ledger allocations | +| Unlock delay | Escrow deposits include an unlock delay after which funds are automatically unlocked to the node if not challenged | +| ChallengeDuration | A period after a challenge was initiated that allows resolution. If no finalization state was supplied, the initiate state is finalized, and funds are returned | + +An escrow is not a separate protocol entity with its own state — it is an on-chain record derived from a channel state transition. The escrow exists only between initiation and finalization (or timeout). + +## Cross-Chain Deposit + +To deposit assets from a non-home chain into a channel, the protocol uses a two-phase escrow process: + +1. **Initiate (Escrow Deposit Initiate)** — participants sign a state that creates an escrow. On the home chain, the node's allocation increases to reserve funds. On the non-home chain, the user's deposit is locked in an escrow record with an unlock delay. +2. **Finalize (Escrow Deposit Finalize)** — after the escrow is created, participants sign a state that completes the deposit. On the home chain, the user's allocation increases by the deposited amount. On the non-home chain, the escrowed funds are released to the node's vault. + +If the escrow is not finalized within the unlock delay, the escrowed funds on the non-home chain are automatically unlocked to the Node. Either participant MAY challenge the escrow during the challenge period. Note that it is NOT possible to challenge a deposit escrow after unlock delay has passed as the funds were already unlocked to the Node. + +Cross-chain amounts are validated using WAD normalization to ensure the home-chain node allocation matches the non-home-chain user deposit. + +## Cross-Chain Withdrawal + +To withdraw assets to a non-home chain, the protocol uses a similar two-phase escrow process: + +1. **Initiate (Escrow Withdrawal Initiate)** — participants sign a state that creates an escrow. On the non-home chain, the node locks funds from its vault into the escrow record. +2. **Finalize (Escrow Withdrawal Finalize)** — participants sign a state that completes the withdrawal. On the home chain, the user's allocation decreases. On the non-home chain, the escrowed funds are released to the user. + +If the escrow is not finalized cooperatively, either participant MAY challenge the escrow. + +## Home Chain Migration + +The home chain of a channel MAY be changed through a two-phase migration process: + +1. **Initiate (Migration Initiate)** — participants sign a state that begins the migration. On the current home chain, the state records the target chain allocation. On the target chain, a new channel record is created with status "migrating in" and the node locks funds equal to the user's allocation (validated via WAD normalization). +2. **Finalize (Migration Finalize)** — participants sign a state that completes the migration. On the new home chain, the channel transitions to operating status. On the old home chain, all locked funds are released to the node and the channel is marked as migrated out. + +After migration, the following changes take effect: + +- **Home chain identifier** is updated to reflect the migration +- **Home token address** is updated to reflect the migration +- **Ledger roles** — the former non-home ledger becomes the home ledger; the former home ledger becomes the non-home ledger (and its allocations are zeroed out on finalization) +- **Enforcement target** — all subsequent enforcement operations execute against the new home chain +- **Balances** — the user's allocation is preserved (normalized by decimal precision); the node's allocation is recalculated for the new chain + +VERSION NOTE: Migration transitions are functional but may be refined in future protocol versions. + +## Cross-Chain Replay Protection + +The protocol prevents cross-chain replay through multiple binding mechanisms: + +- **Chain identifier binding** — each ledger is bound to a specific chain identifier. The blockchain layer validates that the home ledger chain identifier matches the current blockchain. This prevents a state signed for one chain from being enforced on another. +- **Channel identifier scoping** — channel identifiers incorporate a protocol version byte, preventing replay across smart contract deployments. The same channel definition on a different protocol version produces a different channel identifier. +- **Escrow identifier uniqueness** — escrow channel identifiers are derived from the home channel identifier and the state version at initiation. This ensures that each escrow operation produces a unique identifier, preventing a completed escrow from being replayed. +- **Ledger validation** — on-chain enforcement validates that both home and non-home ledger's declared decimals match the actual token decimals on the current execution chain, preventing states crafted for a different token from being accepted. Additionally, a specific set of invariants is enforced for security purposes. + +## Current Version Notes + +In the current protocol version: + +- Cross-chain operations require trust in the node to relay state correctly between chains. The node is responsible for submitting escrow initiation and finalization transactions on the appropriate chains. +- Full cross-chain enforcement (trustless bridging) is a planned future improvement. +- Each channel state supports exactly two ledgers: one home ledger and one non-home ledger. This is a V1-specific design constraint; future protocol versions MAY support additional ledger configurations. + +--- + +Previous: [Enforcement and Settlement](enforcement.md) | Next: [Interactions](interactions.md) diff --git a/docs/protocol/cryptography.md b/docs/protocol/cryptography.md new file mode 100644 index 000000000..226123b4e --- /dev/null +++ b/docs/protocol/cryptography.md @@ -0,0 +1,122 @@ +# Cryptography + +Previous: [Terminology](terminology.md) | Next: [State Model](state-model.md) + +--- + +This document defines how protocol objects are encoded, hashed, and signed. + +All rules are described as algorithms and canonical procedures, independent of any specific programming language. + +## Purpose + +Cryptography in the Nitrolite protocol serves three functions: + +1. **Authentication** — proving that a specific participant authorized a state update +2. **Integrity** — ensuring that signed data has not been modified +3. **Replay protection** — preventing previously signed states from being reused in unintended contexts + +## Cryptographic Algorithms + +The protocol uses the following cryptographic primitives. + +**Signature Algorithm** +ECDSA over the secp256k1 curve, producing a 65-byte signature (r, s, v). + +**Hash Function** +Keccak-256, producing a 32-byte digest. + +## Canonical Encoding + +Protocol objects that require signing MUST be encoded into a canonical binary representation before hashing. + +The canonical encoding uses RLP encoding (`abi.encode` in Solidity) as defined in [this paper](https://doi.org/10.48550/arXiv.2009.13769) and by [Ethereum documentation](https://ethereum.org/developers/docs/data-structures-and-encoding/rlp/). This ensures deterministic byte sequences regardless of implementation language. + +## Message Digest Construction + +The digest of a signable payload is constructed as follows: + +1. Encode the object using canonical encoding +2. Prepend the EIP-191 personal message prefix: the ASCII string `"\x19Ethereum Signed Message:\n"` followed by the decimal length of the encoded bytes, then the encoded bytes themselves +3. Compute the Keccak-256 hash of the prefixed message + +The resulting 32-byte digest is the value that is signed. + +## ECDSA Signature Format + +The raw ECDSA signature consists of: + +| Field | Size | Description | +| ----- | -------- | ------------------------ | +| R | 32 bytes | ECDSA r component | +| S | 32 bytes | ECDSA s component | +| V | 1 byte | Recovery identifier | + +The signer's address is recovered from the signature and the message digest. The protocol does not transmit the signer's public key or address alongside the signature. + +## Protocol Signature Envelope + +A protocol signature is a wrapper around the raw ECDSA signature that includes a validation mode prefix: + +``` +ProtocolSignature = ValidationMode || SignatureData +``` + +The first byte (`ValidationMode`) determines the validation method, which must map to a signature validator registered by the Node on the Smart Contract infrastructure. The remaining bytes (`SignatureData`) contain mode-specific data including the raw signature. + +## Signature Validation Modes + +The protocol supports multiple signature validation modes to allow different key types and authorization schemes. + +**Default Mode (0x00)** +Standard ECDSA signature validation. SignatureData contains the raw ECDSA signature (R, S, V). The signer's address is recovered from the signature. The recovered address MUST match the expected participant address. + +**Session Key Mode (0x01)** +Delegated signature validation. SignatureData contains a session key authorization and the session key's ECDSA signature over the state data, ABI-encoded as a tuple. The validator first verifies that the participant authorized the session key, then verifies that the session key produced a valid signature over the state. The session key authorization MUST be associated with the same address as the channel's user or node participant. The recovered session key address MUST match the address authorized by the participant. + +Session-key signatures are valid for both off-chain state advancement and on-chain enforcement, provided the session key validation mode is among the channel's approved signature validators. + +## Signable Object Classes + +The protocol defines a general signing framework that accommodates multiple classes of signable objects: + +- **Channel Objects**: primarily, the state of a channel, but also a session key registration and challenger signature +- **Extension Objects**: primarily, the state of an extension entity (such as an application session), signed by the relevant session participants + +Please note that channel and extension states are identified by a unique entity identifier and follows the same canonical encoding and digest construction rules. + +This framework is extensible: future protocol extensions MAY introduce additional signable object classes without requiring changes to the core signing rules. + +## Session Key Authorization + +A participant MAY delegate signing authority to a session key. + +The authorization is constructed as follows: + +1. The participant signs a message containing: + - the session key address + - authorization metadata hash (`keccak256` over scope, expiration and possible other data) +2. The authorization signature is produced using the participant's primary key +3. The session key MAY then produce signatures on behalf of the participant within the authorized scope + +Session key signatures MUST include the authorization proof alongside the session key signature. The authorization proof is canonically encoded as a tuple containing the session key authorization and the raw signature bytes. + +## Replay Protection + +The protocol prevents replay attacks through the following mechanisms: + +**Entity Identifier** +Each signable entity has a unique identifier derived from its definition. Signed states are bound to a specific entity, preventing a signature over one entity's state from being replayed against another. + +**State Version** +Each state includes a monotonically increasing version number. The blockchain layer MUST reject states with a version less than or equal to the currently enforced version. + +**Blockchain Identifier** +States include blockchain-specific identifiers preventing cross-chain replay. + +**Smart Contract Version** +The channel entity identifier incorporates a contract version (currently as the first byte), preventing replay across different deployments. + +--- + +Previous: [Terminology](terminology.md) | Next: [State Model](state-model.md) diff --git a/docs/protocol/enforcement.md b/docs/protocol/enforcement.md new file mode 100644 index 000000000..ae8820863 --- /dev/null +++ b/docs/protocol/enforcement.md @@ -0,0 +1,176 @@ +# State Enforcement + +Previous: [Channel Protocol](channel-protocol.md) | Next: [Cross-Chain and Assets](cross-chain-and-assets.md) + +--- + +This document describes how channel states are enforced on the blockchain layer. + +## Purpose + +Enforcement is the mechanism by which off-chain state is reflected on-chain. It serves two complementary roles: + +1. **Regular state synchronization** — participants submit signed states to the blockchain layer to keep the on-chain record up-to-date with the latest off-chain state, particularly for transitions that require on-chain effects (deposits, withdrawals, escrow operations, migrations) +2. **Dispute resolution** — any participant MAY independently submit the latest mutually signed state to the blockchain layer to protect their assets if off-chain cooperation fails + +The blockchain layer acts as the ultimate arbiter of channel state, providing security guarantees that do not depend on participant cooperation. + +## Enforceable State Requirements + +A state is enforceable on-chain if and only if: + +- It is **mutually signed** — it carries valid signatures from both the user and the node +- The signatures use validation modes that are among the channel's approved signature validators (including session-key signatures if the session key validation mode is approved) +- The state has passed off-chain state advancement validation +- The node has sufficient balance on the target chain to cover any required fund locking + +Node-issued pending states (those carrying only the node's signature) are NOT enforceable. They become enforceable only after the user acknowledges them, producing a mutually signed state. + +## Enforcement Model + +Off-chain states and on-chain enforcement states are related as follows: + +- Participants advance state off-chain through signed updates +- At any time, any party MAY submit the latest mutually signed state to the blockchain layer +- The blockchain layer validates the submitted state and updates its record +- On-chain state always reflects the latest successfully checkpointed state + +The on-chain state MAY lag behind the off-chain state. This is expected during normal operation for transitions with the OPERATE intent. + +## Locked Funds Model + +The blockchain layer tracks **locked funds** for each channel. Locked funds represent the total assets held by the enforcement contract on behalf of the channel. + +Rules: + +- Locked funds increase when assets are pulled from the user or from the node's vault into the channel +- Locked funds decrease when assets are released to the user or to the node +- Unless the channel is being closed, the sum of UserAllocation and NodeAllocation in the enforced state MUST equal the locked funds +- Locked funds MUST never be negative + +The node maintains a **vault balance** per token on each chain. The vault is a pool of available funds separate from any specific channel. When a transition requires the node to lock additional funds, the required amount is deducted from the node's vault balance and added to the channel's locked funds. + +| Operation | User Fund Effect | Node Fund Effect | Locked Funds Effect | +| ---------- | ----------------------------------------- | ------------------------------------------ | ----------------------------- | +| DEPOSIT | Pull from user (positive delta) | Adjusted by node net flow delta | Increases by total deltas | +| WITHDRAW | Release to user (negative delta) | Adjusted by node net flow delta | Decreases by total deltas | +| OPERATE | No user fund movement | Adjusted by node net flow delta | Adjusted by node delta only | +| CLOSE | Release UserAllocation to user | Release NodeAllocation to node | Set to zero | +| Challenge | No fund movement | No fund movement | Unchanged (status changes) | + +## Channel Creation + +Channels are created through an enforcement operation. A channel does not need to be created on-chain with its initial off-chain-created state — any validly signed state MAY be used for on-chain creation, provided the channel does not yet exist on-chain. This allows participants to advance state off-chain before enforcing the channel on-chain, e.g. when the user's first action is to receive a transfer from another user, they can additionally perform several transfer send or receive operations before submitting the state on-chain with a "WITHDRAW" intent, receiving funds simultaneously with creating a channel, both on-chain. + +The creation process: + +1. Participants agree on a channel definition and exchange signed state updates off-chain +2. A participant submits the channel definition and a signed state to the blockchain layer +3. The blockchain layer validates signatures, creates the channel record, and applies fund effects according to the state's intent +4. The channel is now active on the on-chain layer + +The state submitted for channel creation MAY carry a DEPOSIT, WITHDRAW, or OPERATE intent. + +## State Submission + +State submission covers checkpoint, deposit, and withdrawal operations. The general process is identical for all three: + +1. A participant constructs the enforcement representation of a signed state +2. The participant submits the enforcement representation along with all required signatures to the blockchain layer +3. The blockchain layer validates the submission +4. If valid, the on-chain state is updated and fund movements are applied + +The behaviour differs only in intent-specific validation rules: + +- **OPERATE** — the blockchain layer validates that the user net flow has not changed and that the node allocation is zero. No user fund movement occurs. +- **DEPOSIT** — the blockchain layer validates that the user net flow delta is positive (assets are flowing in). The deposited amount is pulled from the user and added to the channel's locked funds. +- **WITHDRAW** — the blockchain layer validates that the user net flow delta is negative (assets are flowing out). The withdrawn amount is released from the channel's locked funds to the user. + +In all cases, the node's fund delta is adjusted according to the node net flow change. + +## Challenge Operation + +A challenge allows a participant to dispute the current on-chain state by submitting a signed state along with a separate challenger signature. + +### Challenger Signature + +The challenger signature is distinct from the state signatures. It is produced by signing the enforcement representation of the candidate state with the string "challenge" appended to the signing data. This guarantees that only a User or a Node can start a challenge, and not the third-party. However, a channel participant MAY share a valid challenger signature with a third-party, who then can successfully initiate a challenge. + +**Only** the user or the node MAY act as the challenger. + +### Challenge Process + +1. The challenger submits a candidate state, state signatures, the challenger signature, and the challenger's participant index +2. The channel MUST NOT be in DISPUTE, MIGRATED_OUT or CLOSED statuses +3. The candidate version MUST be greater than or equal to the current on-chain version +4. If the candidate version is strictly greater than the current on-chain version, the blockchain layer validates and applies the new state (including fund effects) +5. The channel status is set to **DISPUTED** and the challenge expiry is set to the current time plus the challenge duration + +### Resolving a Challenge + +During the challenge period, any participant MAY respond by submitting a new valid state whose version is strictly greater than the currently disputed state. This replaces the disputed state, changes channel's status (transitions out from DISPUTED) and clears the challenge timer. + +It should be noted that it is NOT possible to file another challenge on a channel that is already disputed. The current challenge must be resolved first. + +Additionally, it is possible to close the channel unilaterally by submitting a valid "CLOSE" state (if present) even after a channel was challenged. In such case, the channel will transition to CLOSED status immediately, transferring out all funds to the User and the Node according to amounts agreed about in the CLOSE state. + +### Challenge Finality + +After the challenge period expires without being resolved, the disputed state becomes **final**. However, a separate **close call** is still required to release the channel's locked funds. Such close call does not require any state to be submitted alongside, only the id of a channel, and can be invoked by anyone. + +## Close Operation + +A close releases the channel's locked funds and terminates the channel lifecycle. + +Two paths exist: + +**Cooperative close** — a participant submits a state with the CLOSE intent, signed by all participants. The blockchain layer validates that amounts from the allocations are moved to the respective net flows (basically, it is a withdrawal operation). It should be noted that it is not possible to close an already CLOSED or MIGRATED_OUT channel. + +**Unilateral close** — after a challenge period has expired, any party MAY call close without additional signatures. The blockchain layer releases assets according to the last enforced state's allocations (UserAllocation to the user, NodeAllocation to the node). + +In both cases, the channel's locked funds are set to zero and the channel lifecycle ends. + +## Enforcement Validation + +The blockchain layer applies the following common validation rules when processing any enforcement operation: + +1. The submitted state MUST reference the correct channel identifier +2. The home ledger chain identifier MUST match the current blockchain +3. The state version MUST be strictly greater than the currently recorded version +4. All required signatures MUST be present and valid +5. The approved signature validation modes MUST be respected +6. The ledger invariant MUST hold: UserAllocation + NodeAllocation == UserNetFlow + NodeNetFlow +7. The resulting locked funds (previous locked funds plus user and node fund deltas) MUST be non-negative +8. Unless the channel is being closed, the sum of allocations MUST equal the resulting locked funds +9. The node MUST have sufficient available funds in its vault when required to lock additional assets + +## Escrow and Migration Enforcement + +Cross-chain transitions are enforced through dedicated operations on the blockchain layer. The detailed escrow model is described in [Cross-Chain and Assets](cross-chain-and-assets.md). The following summarizes the on-chain effects: + +| Operation | On-Chain Effect | +| -------------------------- | --------------------------------------------------------------------------- | +| Escrow Deposit Initiate | On home chain: state updated, node funds adjusted. On non-home chain: escrow record created, user funds locked. | +| Escrow Deposit Finalize | On home chain: state updated, user allocation increased. On home chain: state updated, node funds adjusted. On non-home chain: escrow record created, user funds locked, automatic release to the Node timer started. | +| Escrow Withdrawal Initiate | On home chain: state updated. On non-home chain: escrow record created, node funds locked from vault. | +| Escrow Withdrawal Finalize | On home chain: state updated, user allocation decreased. On non-home chain: escrowed funds released to user. | +| Migration Initiate | On old home chain: state updated. On new home chain: channel created with migrating-in status, node funds locked. | +| Migration Finalize | On new home chain: channel transitions to operating. On old home chain: all locked funds released, channel marked as migrated out. | + +## Failure Conditions + +Enforcement MAY fail in the following situations: + +- **Invalid signatures** — one or more signatures cannot be verified +- **Stale version** — the submitted state version is not greater than the current on-chain version +- **Inconsistent allocations** — the ledger invariant is violated or resulting locked funds would be negative +- **Allocation-locked-funds mismatch** — the sum of allocations does not equal the expected locked funds (except during close) +- **Unknown channel** — the channel identifier does not correspond to a registered channel (except for channel creation) +- **Insufficient node funds** — the node's vault does not have enough assets to cover required fund locking +- **Invalid intent** — the transition intent does not match the expected operation +- **Chain mismatch** — the home / non-home ledger chain identifier does not match the current blockchain during home-chain / escrow operations +- **Incorrect channel status** — the operation is not permitted in the channel's current status (e.g. challenging an already challenged channel) + +--- + +Previous: [Channel Protocol](channel-protocol.md) | Next: [Cross-Chain and Assets](cross-chain-and-assets.md) diff --git a/docs/protocol/interactions.md b/docs/protocol/interactions.md new file mode 100644 index 000000000..6a3c90ceb --- /dev/null +++ b/docs/protocol/interactions.md @@ -0,0 +1,124 @@ +# Interaction Model + +Previous: [Cross-Chain and Assets](cross-chain-and-assets.md) | Next: [Security and Limitations](security-and-limitations.md) + +--- + +This document defines the logical communication protocol between participants. + +All operations are defined as semantic protocol operations, independent of transport technologies such as WebSocket or gRPC. + +## Purpose + +Participants exchange protocol messages to advance state, manage channels, and coordinate operations. This document defines the structure and semantics of those messages. + +## Connection Assumptions + +The protocol assumes the following about the communication channel: + +- Messages are delivered reliably (no silent loss) +- Messages are delivered in order between any two participants +- The transport supports bidirectional message exchange + +The protocol does not require a specific transport technology. + +## Message Envelope + +All protocol messages share a common envelope structure. + +| Field | Description | +| --------- | --------------------------------------------------- | +| Type | Message type (request, response, event, or error) | +| RequestId | Numeric identifier unique within the connection | +| Method | Operation name identifying the requested action | +| Payload | Type-specific message data | +| Timestamp | Time the message was created, in milliseconds | + +Messages are encoded as compact ordered arrays: [Type, RequestId, Method, Payload, Timestamp]. + +## Message Types + +| Type | +| ---------------------------------------- | +| Request | +| Successful response | +| Event notification | +| Error response | + +## Core Operations + +The protocol defines the following core operations: + +| Operation | Direction | Description | +| ----------------- | ------------- | ---------------------------------------------- | +| RequestCreation | User → Node | Request to create a new channel | +| SubmitState | User → Node | Submit a signed state transition | +| GetLatestState | User → Node | Retrieve the current state for a channel | +| GetHomeChannel | User → Node | Retrieve on-chain home channel data | +| GetEscrowChannel | User → Node | Retrieve on-chain escrow channel data | + +### Operation: RequestCreation + +Creates a new channel with an initial state. + +The request MUST include the channel definition parameters, the initial state and the user's signature over it. The node validates the channel definition, computes the channel identifier, verifies the user's signature, co-signs the state, and stores the channel record. + +The response includes Node's signature over the submitted state. + +### Operation: SubmitState + +Submits a user-signed state transition for processing. + +The request MUST include the signed state with a valid transition. The node validates the state against advancement rules, verifies the user's signature, co-signs the state, and applies any side effects (e.g. scheduling blockchain operations for non-OPERATE intents, creating receiver states for transfers). + +The response includes Node's signature over the submitted state. + +### Operation: GetLatestState + +Retrieves the current state for a given user and asset. + +The response includes the latest state. Implementations MAY support filtering to return only mutually signed states. + +### Operation: GetHomeChannel + +Retrieves the on-chain home channel data for a given user and asset. + +### Operation: GetEscrowChannel + +Retrieves the on-chain escrow channel data for a given escrow channel identifier. + +## Event Messages + +The event message system is reserved for future specification. Events are asynchronous notifications generated by the protocol and are not responses to specific requests. + +## Correlation and Identifiers + +Responses are correlated with requests using the RequestId field. + +Rules: + +- Each request MUST include a RequestId unique within the connection +- The corresponding response MUST include the same RequestId + +## Error Handling + +Errors are communicated through error response messages. + +Rules: + +- Every failed operation MUST return an error response +- The error payload MUST contain a human-readable error message +- Errors MUST NOT expose internal implementation details + +## Message Ordering + +Message ordering requirements MAY depend on the implementation. The following constraints apply at the protocol level: + +- RequestId values MUST NOT be reused within a single connection +- Events MAY arrive at any time and MUST NOT block request processing + +State update ordering (version sequencing) is governed by the [Channel Protocol](channel-protocol.md) and is not a concern of the message transport layer. + +--- + +Previous: [Cross-Chain and Assets](cross-chain-and-assets.md) | Next: [Security and Limitations](security-and-limitations.md) diff --git a/docs/protocol/overview.md b/docs/protocol/overview.md new file mode 100644 index 000000000..58da06d92 --- /dev/null +++ b/docs/protocol/overview.md @@ -0,0 +1,97 @@ +# Nitrolite Protocol Overview + +Nitrolite is a state channel protocol that enables high-speed off-chain interactions between users while preserving on-chain security guarantees. + +Users exchange signed state updates off-chain with Nodes that act as a hub connecting network participants. Any user can enforce the latest agreed state on the blockchain layer at any time. + +## Table of Contents + +1. [Overview](overview.md) — high-level protocol description and design goals +2. [Terminology](terminology.md) — canonical definitions of all protocol terms +3. [Cryptography](cryptography.md) — encoding, hashing, signing, and replay protection +4. [State Model](state-model.md) — state structure, versioning, and consistency rules +5. [Channel Protocol](channel-protocol.md) — channel lifecycle, transitions, and advancement rules +6. [State Enforcement](enforcement.md) — checkpoints, on-chain validation, and enforcement +7. [Cross-Chain and Assets](cross-chain-and-assets.md) — unified asset model and cross-chain operations +8. [Interactions](interactions.md) — message envelope, core operations, and events +9. [Security and Limitations](security-and-limitations.md) — security guarantees, trust assumptions, and known limitations +10. [Extensions](extensions/overview.md) — extension model, lifecycle, and safety constraints + + +## Design Goals + +The protocol is designed to achieve: + +- **Off-chain scalability** — minimize on-chain transactions by moving state advancement off-chain +- **Blockchain security guarantees** — any user can fall back to the blockchain layer to enforce the latest state +- **Cross-chain asset interaction** — operate on assets across multiple blockchains through a unified model +- **Extensibility** — support additional functionality through protocol extensions without modifying the core protocol + +## System Roles + +The protocol defines the following roles. + +**User** +An entity that opens channels, signs state updates, and holds assets within the protocol. + +**Node** +An entity that facilitates off-chain state advancement, manages channels, and syncs with the blockchain layer. + +**Blockchain** +The on-chain storage and execution layer that validates enforceable incoming states according to the protocol rules, stores states and resolves disputes. + +## High-Level Architecture + +The system operates in three conceptual layers: + +1. **Protocol layer** — defines rules for state validity, advancement, and enforcement +2. **Off-chain layer** — signed state updates exchange with a node +3. **Blockchain layer** — blockchain contracts that hold assets and enforce states + +## Core Protocol Concepts + +**Channels** +A channel is a state container shared between a Node and a User. It holds user asset allocations and supports off-chain state updates. Each channel is defined by immutable parameters including the participants, asset, challenge duration, and approved signature validators. + +**States** +A state represents the current agreed asset allocations and metadata shared between a Node and a User. Each state contains two ledgers (home and non-home), a version number, and a transition describing the operation that produced it. + +**State Advancement** +User and a node advance states off-chain by exchanging signed state transitions. Each new state MUST have a version exactly one greater than the previous state. Transitions include deposits, withdrawals, transfers, commits, releases, escrow operations, and migrations. + +**State Enforcement** +Any party MAY submit the latest signed state to the blockchain layer for on-chain enforcement. The blockchain layer validates signatures, version ordering, and ledger invariants before accepting a state. + +**Unified Assets** +The same asset from multiple blockchains is represented in a unified model, enabling cross-chain operations among users and apps. The protocol normalizes amounts by decimal precision when comparing allocations across chains. + +**Extensions** +Additional protocol functionality, such as application sessions, is provided through the extension layer without modifying core protocol rules. Extensions interact with channels through commit and release transitions. + +## Protocol Layers + +The protocol separates responsibilities into distinct layers. + +**Core Protocol** +Defines channels, states, state advancement rules, and enforcement mechanisms. + +**Extension Layer** +Provides additional functionality such as application sessions. Extensions interact with the core protocol through defined interfaces. + +**Blockchain Layer** +Blockchain contracts that create channels, hold deposits, accept state checkpoints, manage escrow operations, and release funds. + +## Protocol Version + +This documentation describes Nitrolite Protocol V1. + +Compatibility expectations: + +- State structures and signing rules defined in this version are stable +- Extension interfaces may evolve in future versions +- Blockchain layer contracts are version-specific +- ChannelIDs are generated by including protocol version into a hashing function to prevent cross-version replay + +--- + +Next: [Terminology](terminology.md) diff --git a/docs/protocol/security-and-limitations.md b/docs/protocol/security-and-limitations.md new file mode 100644 index 000000000..c7dae6e2e --- /dev/null +++ b/docs/protocol/security-and-limitations.md @@ -0,0 +1,92 @@ +# Security and Limitations + +Previous: [Interactions](interactions.md) | Next: [Extensions Overview](extensions/overview.md) + +--- + +This document describes the security guarantees of the Nitrolite protocol, its current trust assumptions, and the known limitations of the present version. + +## Protocol Maturity + +The core protocol functionality is implemented and operational. A user MAY operate over a unified asset, deposit and withdraw on any supported blockchain, and conduct the majority of interactions without direct blockchain involvement. The protocol provides protection against unauthorized state changes from the user side — no user can unilaterally alter the state without valid signatures from all required participants. + +However, the protocol in its current form is not fully trust-minimized. The primary remaining trust assumption concerns node behaviour and liquidity, as described in the sections below. The protocol is under active development, with planned improvements to address these limitations. + +## Security Goals + +The protocol aims to guarantee: + +- **Asset safety** — participants MUST NOT lose assets without signing a state that authorizes the change +- **State finality** — the latest mutually signed state can always be enforced on-chain +- **Non-repudiation** — a participant cannot deny having signed a state +- **Censorship resistance** — any party MAY independently enforce state on the blockchain layer + +## Off-Chain Safety + +The protocol protects against invalid or malicious state submissions through: + +**Signature requirements** +Every state update requires valid signatures from all required participants. No participant can unilaterally change the state. + +**Version ordering** +State versions are strictly increasing. Old states cannot replace newer states. + +**Asset conservation** +State transitions MUST preserve total asset amounts within each ledger. No assets can be created or destroyed through state updates. + +**Transition validation** +Each state update MUST satisfy transition-specific rules. Invalid transitions are rejected. + +## Enforcement Guarantees + +The blockchain layer provides the following guarantees: + +- Any party MAY submit the latest signed state at any time +- The blockchain layer accepts only states with valid signatures and a higher version than the current on-chain state +- After the challenge period, the enforced state becomes final +- Final state allocations determine asset distribution + +## Node Liquidity and Cross-Chain Trust + +Each user channel is opened with a node. To maintain cross-chain functionality, the node MUST hold sufficient liquidity on each supported blockchain to satisfy off-chain state allocations. + +When a user with home chain A transfers assets to a user with home chain B, the node receives the amount on chain A and allocates from its own balance to the recipient on chain B. This process occurs entirely off-chain. If the recipient subsequently wishes to enforce their state on chain B and the node does not hold sufficient liquidity on that chain, the on-chain enforcement will fail. + +In the current protocol version, this constitutes a trust assumption: users rely on the node operator to maintain adequate liquidity across all supported chains. Node operators are expected to manage their liquidity to cover off-chain obligations, but users cannot independently verify that this condition holds at all times. + +## Current Trust Assumptions + +In the current protocol version, participants MUST trust nodes for: + +- **Liveness** — nodes MUST be online to facilitate off-chain state advancement +- **Cross-chain liquidity** — nodes MUST maintain sufficient funds on each supported chain to honour off-chain allocations; insufficient liquidity may cause on-chain enforcement to fail +- **Cross-chain relay** — nodes relay cross-chain state updates; trustless cross-chain enforcement is not yet implemented +- **Timely enforcement** — nodes are expected to submit checkpoints when requested; delayed enforcement may affect user experience but does not compromise single-chain asset safety + +Participants do not need to trust nodes for: + +- **Single-chain asset custody** — assets on the home chain can always be recovered through on-chain enforcement +- **State validity** — invalid states are rejected by signature and validation rules + +## Known Limitations + +The following capabilities are not yet implemented: + +- Trustless off-chain state operations (node liquidity enforcement) +- Validator network for monitoring node behaviour and enforcing correctness +- Watchtower services for automated enforcement +- Support for non-EVM blockchains +- Formal verification of protocol rules + +## Future Improvements + +The protocol roadmap includes the following planned improvements: + +- **Validator network** — off-chain state advancement can be independently validated; a validator network would monitor on-chain actions and penalize node misbehaviour that harms the ecosystem +- **Extension layer on-chain enforcement** — removing the reliance on node liquidity trust for extension layer operations +- **Non-EVM blockchain support** — redesigning the protocol to support blockchains beyond the EVM ecosystem (planned for V2) +- **Watchtower integration** — automated monitoring and enforcement on behalf of users + +--- + +Previous: [Interactions](interactions.md) | Next: [Extensions Overview](extensions/overview.md) diff --git a/docs/protocol/state-model.md b/docs/protocol/state-model.md new file mode 100644 index 000000000..51543782d --- /dev/null +++ b/docs/protocol/state-model.md @@ -0,0 +1,175 @@ +# State Model + +Previous: [Cryptography](cryptography.md) | Next: [Channel Protocol](channel-protocol.md) + +--- + +This document describes the abstract structure of protocol states. + +It explains how states are defined and structured. Operational flows are described in separate documents. + +## Purpose + +States represent the current agreed configuration of protocol entities. The state model defines: + +- what information a state contains +- how states are identified and versioned +- how states are represented for off-chain and on-chain use + +## Common State Fields + +All protocol states share the following common properties: + +| Field | Description | +| -------- | -------------------------------------------------------------- | +| EntityId | 32-byte unique identifier of the entity this state belongs to | +| Version | 64-bit unsigned integer, monotonically increasing | + +In addition to these common fields, each state contains entity-specific data whose structure varies depending on the entity type and use case. The entity-specific data is defined by the respective entity specification. + +## State Identification and Versioning + +Each state is identified by the combination of its entity identifier and version number. + +Rules: + +- The entity identifier is derived from the entity definition and is immutable +- The version MUST start at 1 for the initial state +- Versions are strictly increasing; the exact increment rule depends on the context: + - Off-chain state advancement requires each new version to be exactly the previous version plus one + - On-chain enforcement requires only that the submitted version be strictly greater than the currently recorded on-chain version + +## Channel State + +The channel state is the primary protocol state. It represents the current configuration of a channel. + +| Field | Description | +| ------------- | ------------------------------------------------------ | +| ChannelId | 32-byte identifier derived from the channel definition | +| Metadata | 32-byte Hash of channel metadata | +| Version | 64-bit unsigned integer, state version | +| HomeLedger | Asset allocations on the home chain | +| NonHomeLedger | Asset allocations on the non-home chain | +| Transition | Describes the operation that produced this state | +| UserSig | User signature for the state | +| NodeSig | Node signature for the state | + +The channel identifier encodes a protocol version byte as its first byte, followed by the hash of the channel definition parameters. This ensures uniqueness across protocol deployments. + +### Ledger + +A ledger records asset allocations for a specific blockchain within a channel. Each channel state contains exactly two ledgers: a home ledger and a non-home ledger. + +| Field | Description | +| -------------- | ------------------------------------------------------------ | +| ChainId | Identifier of the blockchain this ledger is associated with | +| Token | Token contract address on this chain | +| Decimals | Decimal precision of the token on this chain | +| UserAllocation | Amount allocated to the user | +| UserNetFlow | Cumulative net flow for the user (may be negative) | +| NodeAllocation | Amount allocated to the node | +| NodeNetFlow | Cumulative net flow for the node (may be negative) | + +**Ledger invariant:** A ledger MUST satisfy the following invariant at all times: + +``` +UserAllocation + NodeAllocation == UserNetFlow + NodeNetFlow +``` + +UserNetFlow tracks the cumulative net amount that has flowed into or out of the user's position through deposits, withdrawals, and cross-chain operations. NodeNetFlow tracks the cumulative net amount that has flowed through the node's position, including transfers, commits, and releases. Allocations represent the current distributable balances. The invariant ensures that the total distributable balance always equals the total cumulative flows — no assets can be created or destroyed through state transitions. + +All allocation values MUST be non-negative. Net flow values MAY be negative, reflecting outbound transfers or withdrawals that exceed inbound flows. + +### Empty Non-Home Ledger + +When a channel state does not involve cross-chain operations, the non-home ledger MUST be empty. An empty non-home ledger is defined as a ledger where all fields are set to their zero values: + +| Field | Value | +| -------------- | ------------------------------------------ | +| ChainId | 0 | +| Token | Zero address (0x0000...0000) | +| Decimals | 0 | +| UserAllocation | 0 | +| UserNetFlow | 0 | +| NodeAllocation | 0 | +| NodeNetFlow | 0 | + +An empty non-home ledger is structurally present but zeroed. A non-home ledger with metadata (non-zero ChainId or Token) but zero balances is NOT considered empty. + +## Off-Chain Representation + +The off-chain representation is the primary operational format of a channel state. It is the representation exchanged between participants during state advancement, and it is the representation that is signed. + +The off-chain representation contains all channel state fields directly, including the full transition data (type, transaction identifier, account identifier, and amount). This representation is optimized for human readability, ease of validation, and efficient signature generation. + +## Enforcement Representation + +The off-chain and on-chain (enforcement) representations depict the **same logical state**. The on-chain (enforcement) representation is derived deterministically from the off-chain one — no additional information is required. + +When a state is submitted to the blockchain layer, it uses an enforcement representation optimized for on-chain verification, gas efficiency, and deterministic encoding. + +The following fields are preserved exactly from the off-chain representation: + +- Version +- Home and non-home ledger fields (ChainId, Token, Decimals, UserAllocation, UserNetFlow, NodeAllocation, NodeNetFlow) + +The following fields are derived: + +- **Intent** — derived from the transition type via the intent mapping table +- **MetadataHash** — the Keccak-256 hash of the ABI-encoded transition data (type, transaction identifier, account identifier, and amount). This captures all off-chain transition information in a single hash, ensuring that the enforcement representation is bound to the specific transition without transmitting the full transition data on-chain. + +The enforcement representation is constructed by packing these fields into an ABI-encoded structure: + +``` +SignablePayload = AbiEncode(ChannelId, AbiEncode(Version, Intent, MetadataHash, HomeLedger, NonHomeLedger)) +``` + +Where each ledger is encoded as a tuple of (chain identifier, token address, decimals, user allocation, user net flow, node allocation, node net flow). + +Because the mapping is deterministic, both the off-chain and enforcement representations produce the same message digest when signed, ensuring that a signature over the off-chain state is valid for enforcement and vice versa. + +## Intent Mapping + +Each transition type maps to an intent value used in the enforcement representation. The intent determines how the blockchain layer processes the state. + +| On-chain Intent | Transition | +| -------------------------- | --------------------------- | +| OPERATE | TransferSend, TransferReceive, Commit, Release, Acknowledgement | +| CLOSE | Finalize | +| DEPOSIT | Home Deposit | +| WITHDRAW | Home Withdrawal | +| INITIATE_ESCROW_DEPOSIT | Escrow Deposit Initiate | +| FINALIZE_ESCROW_DEPOSIT | Escrow Deposit Finalize | +| INITIATE_ESCROW_WITHDRAWAL | Escrow Withdrawal Initiate | +| FINALIZE_ESCROW_WITHDRAWAL | Escrow Withdrawal Finalize | +| INITIATE_MIGRATION | Migration Initiate | +| FINALIZE_MIGRATION | Migration Finalize | + +Transitions that map to the OPERATE intent do not require on-chain checkpointing under normal operation. + +## Transition Field + +Each state update includes a transition that describes the operation that produced the new state. + +| Field | Description | +| --------- | ---------------------------------------------------------------- | +| Type | Transition type identifier | +| TxId | Transaction identifier hash | +| AccountId | Context-dependent account identifier (varies by transition type) | +| Amount | Amount involved in the transition | + +The transition type determines the validation rules applied to the state update. The account identifier carries different semantics depending on the transition type — for example, it references the channel identifier for deposit and withdrawal operations, the counterparty address for transfers, or the application session identifier for commit and release operations. + +## State Consistency Rules + +State validity requirements differ between off-chain advancement and on-chain enforcement contexts. Off-chain advancement rules are defined in the [Channel Protocol](channel-protocol.md) document, and on-chain enforcement rules are defined in the [State Enforcement](enforcement.md) document. + +In both contexts, the following invariants MUST hold: + +- The entity identifier MUST match the entity definition +- The version MUST be strictly greater than the previously accepted version +- Ledger invariants MUST be satisfied (allocations equal net flows, allocation values non-negative) + +--- + +Previous: [Cryptography](cryptography.md) | Next: [Channel Protocol](channel-protocol.md) diff --git a/docs/protocol/state_ledger_advancement.png b/docs/protocol/state_ledger_advancement.png new file mode 100644 index 000000000..aa4653d27 Binary files /dev/null and b/docs/protocol/state_ledger_advancement.png differ diff --git a/docs/protocol/terminology.md b/docs/protocol/terminology.md new file mode 100644 index 000000000..e6d266daf --- /dev/null +++ b/docs/protocol/terminology.md @@ -0,0 +1,175 @@ +# Terminology + +Previous: [Overview](overview.md) | Next: [Cryptography](cryptography.md) + +--- + +This document defines all protocol terms used throughout the Nitrolite protocol documentation. + +Each term is defined once. All other documents MUST use these terms consistently. + +## Naming Conventions + +- Protocol entities use CamelCase (e.g., ChannelState, AppSession) +- Field names use CamelCase (e.g., ChannelId, StateVersion) +- Operations use lowercase with hyphens in document references (e.g., state-advancement) + +## Core Entities + +### Channel + +A state container shared between a user and a node that allows off-chain state updates while maintaining on-chain security guarantees. Each channel operates on a single unified asset. + +### Channel Definition + +The immutable parameters that define a channel: user, node, asset, nonce, challenge duration, and approved signature validators. A channel definition is fixed at creation time and MUST NOT change during the channel lifecycle. + +### Channel State + +The current agreed configuration of a channel, including home and non-home ledger allocations, a version number, and a transition field. Channel state evolves through off-chain state advancement. + +### Participant + +An entity that holds a signing key and participates in a channel. Each channel has exactly two participants: a user and a node. + +### Asset + +A representation of value within the protocol, identified by a human-readable symbol and decimal precision. Assets are identified independently of any specific blockchain; the same logical asset MAY exist on multiple chains with different token addresses. + +## State Concepts + +### State + +An abstract data structure representing the current configuration of a protocol entity at a specific version. + +### State Version + +A monotonically increasing integer that identifies the order of state updates. During off-chain advancement, each new state MUST have a version exactly one greater than the previous state. + +### State Advancement + +The process of updating a protocol entity's state off-chain through signed transitions exchanged between participants. + +### State Enforcement + +The process of submitting a signed state to the blockchain layer for on-chain validation and enforcement. + +### Transition + +A typed operation that describes the reason and parameters for a state update. Each transition carries a type, transaction identifier, account identifier, and amount. + +### Intent + +A value derived from the transition type that determines how the blockchain layer processes an enforced state. Intents include OPERATE, CLOSE, DEPOSIT, WITHDRAW, and various escrow and migration intents. + +## Cryptographic Concepts + +### Signature + +A cryptographic proof that a specific key holder authorized a specific message. The protocol uses ECDSA over secp256k1. + +### Signer + +An entity capable of producing signatures. Each signer is associated with a specific key. + +### Session Key + +A delegated signing key authorized by a participant's primary key to sign specific types of state updates on their behalf. Session key authorization MUST be associated with the same address as the channel's user or node participant. + +### Signature Validation Mode + +A mechanism that determines how a signature is verified. The protocol currently defines two modes: default (0x00) for standard ECDSA validation and session key (0x01) for delegated validation. + +## Ledger Concepts + +### Ledger + +A record of asset allocations within a channel, associated with a specific blockchain. Each ledger tracks user and node allocations and net flows, and MUST satisfy the invariant that allocations equal net flows. + +### Home Ledger + +The primary ledger of a channel state, associated with the blockchain where the state is enforced. The home ledger is the authoritative source for channel state enforcement. + +### Non-Home Ledger + +A secondary ledger tracking asset allocations on a blockchain other than the home chain. Used for cross-chain escrow operations and migrations. + +### Home Chain + +The blockchain identified by the home ledger's chain identifier. The home chain determines where enforcement operations are executed. It MAY change through a migration operation. + +### Locked Funds + +The total assets held by the blockchain enforcement contract on behalf of a specific channel. Unless the channel is being closed, the sum of UserAllocation and NodeAllocation MUST equal the locked funds. + +### Vault + +A pool of available funds maintained by the node on a specific blockchain, separate from any specific channel. The vault is used to cover required fund locking when a transition requires the node to lock additional assets into a channel. + +### WAD Normalization + +The process of scaling chain-specific asset amounts to the asset's configured decimal precision for exact, lossless cross-chain comparisons: + +``` +NormalizedAmount = Amount * 10^(18 - ChainDecimals) +``` + +Each unified asset defines a canonical decimal precision (e.g. 6 for USDC) that is used during User <> Clearnode interactions (e.g. on-chain deposit, on-chain state submission requests, transfers, app session operations etc.). The maximum supported decimal precision is 18. + +## State Signing Categories + +### Mutually Signed State + +A state that carries valid signatures from both the user and the node. Only mutually signed states are enforceable on-chain. + +### Node-Issued Pending State + +A state produced by the node that carries only the node's signature. A pending state is NOT enforceable on-chain and becomes mutually signed only after the user acknowledges it. + +### Channel Status + +A specific on-chain channel data configuration, which changes throughout channel lifecycle, and includes *operating*, *disputed*, *migrating-in*, *migrated out*, etc. This can be thought of as a Finite State-Machine State (do not confuse with State Channel State). + +### Escrow Channel Identifier + +A 32-byte hash derived deterministically from the home channel identifier and the state version. Used to uniquely identify each escrow operation. + +## Protocol Operations + +### Checkpoint + +The operation of submitting a signed state to the blockchain layer. A checkpoint records the latest agreed state on-chain. + +### Challenge + +An on-chain operation where a participant disputes the current enforced state by submitting a signed state along with a challenger signature. Initiates the challenge duration, during which other participants MAY respond with a higher-version state. + +### Commit + +The operation of moving assets from a channel into an extension, such as an application session. Decreases the user's allocation and the node's net flow. + +### Release + +The operation of returning assets from an extension back to the channel. Increases the user's allocation and the node's net flow. + +### Escrow + +A two-phase mechanism for cross-chain operations. An "escrow initiate" locks funds, and an "escrow finalize" releases them upon cooperative completion or after a timeout period. + +## Extension Concepts + +### Extension + +An additional protocol module that provides functionality beyond the core channel protocol. Extensions interact with channels through commit and release transitions. + +### Application Session + +An extension that enables off-chain application functionality. Application sessions hold committed assets and maintain their own state. + +### Application State + +The state associated with an application session, tracking committed assets and application-specific data. + +--- + +Previous: [Overview](overview.md) | Next: [Cryptography](cryptography.md) diff --git a/erc7824-docs/.firebaserc b/erc7824-docs/.firebaserc deleted file mode 100644 index 40c8735e8..000000000 --- a/erc7824-docs/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "erc7824" - } -} diff --git a/erc7824-docs/.gitignore b/erc7824-docs/.gitignore deleted file mode 100644 index d3e664850..000000000 --- a/erc7824-docs/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Dependencies -/node_modules - -# Production -/build - -# Generated files -.docusaurus -.cache-loader - -# Misc -.firebase -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -sdk -frontend \ No newline at end of file diff --git a/erc7824-docs/README.md b/erc7824-docs/README.md deleted file mode 100644 index 1a5805b96..000000000 --- a/erc7824-docs/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Website - -This repository hosts the source files from which the [erc7284.org website](https://erc7284.org) is built -using [Docusaurus](https://docusaurus.io/), a modern static website generator. - -### Installation - -``` -$ yarn -``` - -### Local Development - -``` -$ yarn start -``` - -This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. - -### Build - -``` -$ yarn build -``` - -This command generates static content into the `build` directory and can be served using any static contents hosting service. - -### Deployment - -Using SSH: - -``` -$ USE_SSH=true yarn deploy -``` - -Not using SSH: - -``` -$ GIT_USER= yarn deploy -``` - -If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/erc7824-docs/blog/authors.yml b/erc7824-docs/blog/authors.yml deleted file mode 100644 index 8bfa5c7c4..000000000 --- a/erc7824-docs/blog/authors.yml +++ /dev/null @@ -1,23 +0,0 @@ -yangshun: - name: Yangshun Tay - title: Front End Engineer @ Facebook - url: https://github.com/yangshun - image_url: https://github.com/yangshun.png - page: true - socials: - x: yangshunz - github: yangshun - -slorber: - name: Sébastien Lorber - title: Docusaurus maintainer - url: https://sebastienlorber.com - image_url: https://github.com/slorber.png - page: - # customize the url of the author page at /blog/authors/ - permalink: '/all-sebastien-lorber-articles' - socials: - x: sebastienlorber - linkedin: sebastienlorber - github: slorber - newsletter: https://thisweekinreact.com diff --git a/erc7824-docs/blog/tags.yml b/erc7824-docs/blog/tags.yml deleted file mode 100644 index bfaa778fb..000000000 --- a/erc7824-docs/blog/tags.yml +++ /dev/null @@ -1,19 +0,0 @@ -facebook: - label: Facebook - permalink: /facebook - description: Facebook tag description - -hello: - label: Hello - permalink: /hello - description: Hello tag description - -docusaurus: - label: Docusaurus - permalink: /docusaurus - description: Docusaurus tag description - -hola: - label: Hola - permalink: /hola - description: Hola tag description diff --git a/erc7824-docs/docs/_legacy/examples/index.md b/erc7824-docs/docs/_legacy/examples/index.md deleted file mode 100644 index 0cd8cd5ca..000000000 --- a/erc7824-docs/docs/_legacy/examples/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -sidebar_position: 5 -title: Examples -description: State Channels tutorials, application examples in solidity and golang -keywords: [erc7824, statechannels, state channels, ethereum scaling, layer 2, off-chain, tutorial, tictactoe, gaming] -tags: - - erc7824 - - games - - golang - - docs ---- - -# Example Apps diff --git a/erc7824-docs/docs/_legacy/examples/tictactoe.md b/erc7824-docs/docs/_legacy/examples/tictactoe.md deleted file mode 100644 index bb9eefc0a..000000000 --- a/erc7824-docs/docs/_legacy/examples/tictactoe.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -sidebar_position: 3 -title: Gaming with TicTacToe -description: Example of out to manage nitro state using go-nitro SDK -keywords: [erc7824, statechannels, nitro, sdk, development, state channels, ethereum scaling, L2] -tags: - - erc7824 - - golang - - go-nitro - - nitro - - docs ---- -# TicTacToe Offchain - -Below an example in go lang of usage of our upcoming go lang SDK -Typescript will be also available soon. - -```go -package main - -import ( - "errors" - "testing" - - "github.com/ethereum/go-ethereum/common" - ecrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/layer-3/clearsync/pkg/signer" - "github.com/layer-3/neodax/internal/nitro" - "github.com/stretchr/testify/require" -) - -// TicTacToe represents a simple Nitro application implementing a Tic-Tac-Toe game. -type TicTacToe struct { - players []signer.Signer - grid [3][3]byte // 3x3 grid for TicTacToe - - ch nitro.Channel -} - -// NewTicTacToe initializes a new TicTacToe instance. -func NewTicTacToe(players []signer.Signer) *TicTacToe { - fp := nitro.FixedPart{ - Participants: []common.Address{players[0].CommonAddress(), players[1].CommonAddress()}, - } - vp := nitro.VariablePart{} - s := nitro.StateFromFixedAndVariablePart(fp, vp) - - return &TicTacToe{ - players: players, - grid: [3][3]byte{}, - ch: *nitro.NewChannel(s), - } -} - -// Definition returns the FixedPart of the Nitro state. -func (t *TicTacToe) Definition() nitro.FixedPart { - return t.ch.FixedPart -} - -// Data returns the VariablePart representing the current game state. -func (t *TicTacToe) Data(turn uint64) nitro.VariablePart { - return nitro.VariablePart{ - AppData: encodeGrid(t.grid), - Outcome: nitro.Exit{}, - } -} - -func (t *TicTacToe) State(turn uint64) nitro.SignedState { - return t.ch.SignedStateForTurnNum[turn] -} - -// Validate checks if the current state is valid. -func (t *TicTacToe) Validate(turn uint64) bool { - // Validate state can be done using the contract artifact - return true -} - -func (t *TicTacToe) LatestSupportedState() (nitro.SignedState, error) { - return t.ch.LatestSupportedState() -} - -// encodeGrid converts the grid to a byte slice for storage in AppData. -func encodeGrid(grid [3][3]byte) []byte { - var data []byte - // Encode using ABI - for _, row := range grid { - data = append(data, row[:]...) - } - return data -} - -// MakeMove updates the game state with a player's move. -func (t *TicTacToe) MakeMove(player byte, x, y int) error { - if x < 0 || x > 2 || y < 0 || y > 2 { - return errors.New("invalid move: out of bounds") - } - if t.grid[x][y] != 0 { - return errors.New("invalid move: cell already occupied") - } - t.grid[x][y] = player - return nil -} - -func (t *TicTacToe) StartGame() error { - preFundState, err := t.ch.State(nitro.PreFundTurnNum) - if err != nil { - return err - } - - for _, p := range t.players { - if _, err := t.ch.SignAndAddState(preFundState.State(), p); err != nil { - return err - } - } - - return nil -} - -func (t *TicTacToe) FinishGame() error { - lss, err := t.ch.LatestSignedState() - if err != nil { - return err - } - - lastState := lss.State().Clone() - lastState.IsFinal = true - lastState.TurnNum += 1 - - for _, p := range t.players { - if _, err := t.ch.SignAndAddState(lastState, p); err != nil { - return err - } - } - - return nil -} - -func TestTicTacToe(t *testing.T) { - // Create player signers - var alice, bob signer.Signer - - app := NewTicTacToe([]signer.Signer{alice, bob}) - nch, err := nitro.NewClient() // Implements channel.Client - require.NoError(t, err, "Error creating client") - - err = app.StartGame() - require.NoError(t, err, "Error starting game") - - // Open the channel - _, err = nch.Open(app) - require.NoError(t, err, "Error opening channel") - - // Simulate moves - err = app.MakeMove('X', 0, 0) - require.NoError(t, err, "Invalid move") - - err = app.MakeMove('O', 1, 1) - require.NoError(t, err, "Invalid move") - - err = app.FinishGame() - require.NoError(t, err, "Error finishing game") - - // Close the channel at turn 2 - err = nch.Close(app) - require.NoError(t, err, "Error closing channel") -} -``` diff --git a/erc7824-docs/docs/_legacy/faq.md b/erc7824-docs/docs/_legacy/faq.md deleted file mode 100644 index a547155a5..000000000 --- a/erc7824-docs/docs/_legacy/faq.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -sidebar_position: 7 -title: FAQ -description: State channels frequently asked questions -keywords: [erc7824, statechannels, nitro, sdk, faq, state channels, ethereum scaling, L2] -tags: - - erc7824 - - nitro - - faq ---- -# FAQ - -### State channels FAQ - -
- What is ERC-7824? -

- ERC-7824 is a proposed standard for cross-chain trade execution systems that use state channels. It defines structures and interfaces to enable efficient, secure, and scalable off-chain interactions while leveraging the blockchain for finality and dispute resolution. -

-
- -
- What is a state channel? -

- A state channel can be thought of as an account with multiple balances (often just two). The owners of that account can update those balances according to predefined rules, which are enforceable on a blockchain. This enables peer-to-peer games, payments, and other few-user applications to safely trade blockchain assets with extremely low latency, low cost, and high throughput without requiring trust in a third party. -

-
- -
- How do state channels work? -

- 1. Setup: Participants lock assets into a blockchain-based smart contract.
- 2. Off-Chain Updates: Transactions or updates occur off-chain through cryptographically signed messages.
- 3. Finalization: The final state is submitted on-chain for settlement, or disputes are resolved if necessary. -

-
- -
- What are the benefits of state channels? -

- - High Performance: Transactions are processed off-chain, providing low latency and high throughput.
- - Cost Efficiency: Minimal blockchain interactions significantly reduce gas fees.
- - Privacy: Off-chain interactions keep intermediate states confidential.
- - Flexibility: Supports a wide range of applications, including multi-chain trading. -

-
- -
- What kind of applications use state channels? -

- State channels enable the redistribution of assets according to arbitrary logic, making them suitable for: -

    -
  • Games: Peer-to-peer poker or other interactive games.
  • -
  • Payments: Microtransactions and conditional payments.
  • -
  • Swaps: Atomic swaps between assets.
  • -
  • Decentralized Trading: Real-time, high-frequency trading applications.
  • -
-

-
- -
- How is Nitro Protocol implemented? -

- - On-Chain Components: Implemented in Solidity and included in the npm package @statechannels/nitro-protocol.
- - Off-Chain Components: A reference implementation provided through go-nitro, a lightweight client written in Go. -

-
- -
- Where is Nitro Protocol being used? -

- The maintainers of Nitro Protocol are actively integrating it into the Filecoin Retrieval Market and the Filecoin Virtual Machine, enabling decentralized and efficient content distribution. -

-
- -
- What is the structure of a state in state channels? -

- A state consists of: -

    -
  1. Fixed Part: Immutable properties like participants, nonce, app definition, and challenge duration.
  2. -
  3. Variable Part: Changeable properties like outcomes, application data, and turn numbers.
  4. -
- In Nitro, participants sign a keccak256 hash of both these parts to commit to a particular state. The turnNum determines the version of the state, while isFinal can trigger an instant finalization when fully countersigned. -

-
- -
- What is a challenge duration? -

- The challenge duration is a time window during which disputes can be raised on-chain. If no disputes are raised, the state channel finalizes according to its latest agreed state. In Nitro, it is set at channel creation and cannot be changed later. During this period, an unresponsive or dishonest participant can be forced to progress the channel state via on-chain transactions. -

-
- -
- How do disputes get resolved in state channels? -

- Participants can: -

    -
  1. Submit signed updates to the blockchain as evidence.
  2. -
  3. Resolve disputes based on turn numbers and application-specific rules stored in an on-chain appDefinition.
  4. -
  5. Finalize the channel after the challenge duration if no valid disputes arise.
  6. -
- Nitro Protocol introduces a challenge mechanism, enabling any participant to push the channel state on-chain, forcing the other side to respond. This ensures that unresponsive or malicious actors cannot stall the channel indefinitely. -

-
- -
- What is the typical channel lifecycle in Nitro Protocol? -

- A direct channel often follows these stages: -

- 1. Proposed: A participant signs the initial (prefund) state with turnNum=0.
- 2. ReadyToFund: All participants countersign the prefund state, ensuring it is safe to deposit on-chain.
- 3. Funded: Deposits appear on-chain, and participants exchange a postfund state (turnNum=1).
- 4. Running: The channel can be updated off-chain by incrementing turnNum and exchanging signatures.
- 5. Finalized: A state with isFinal=true is fully signed. No more updates are possible; the channel can pay out according to the final outcome. -

-
- -
- How do I finalize and withdraw funds from a direct channel in Nitro? -

- - Finalization (Happy Path): If a fully signed state with isFinal=true exists off-chain, any participant can call conclude on the Nitro Adjudicator to finalize instantly.
- - Finalization (Dispute Path): If participants are unresponsive or disagree, one party can challenge with the latest supported state. After the challenge window, the channel is finalized if unchallenged or out-of-date states are resolved.
- - Withdrawing: Once finalized, participants use the transfer or concludeAndTransferAllAssets method to claim their allocations on-chain. -

-
diff --git a/erc7824-docs/docs/_legacy/index.md b/erc7824-docs/docs/_legacy/index.md deleted file mode 100644 index 9680d9e2c..000000000 --- a/erc7824-docs/docs/_legacy/index.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -sidebar_position: 5 -slug: /legacy -title: Legacy -description: Create high-performance chain agnostic dApps using state channels -keywords: [erc7824, statechannels, chain abstraction, chain agnostic, state channels, ethereum scaling, layer 2, layer 3, nitro, trading, high-speed] -tags: - - erc7824 - - docs ---- - -# Inject Nitro in your Stack - -Blockchain technology has introduced a paradigm shift in how digital assets and decentralized applications (dApps) operate. However, scalability, transaction costs, and cross-chain interoperability remain significant challenges. **ERC-7824** and the **Nitro Protocol** provide a solution by enabling **off-chain state channels**, allowing seamless and efficient transactions without compromising on security or performance. - -ERC-7824 defines a minimal, universal interface for state channel interactions without assuming any specific underlying chain, making it naturally chain agnostic. It abstracts away chain-specific details by specifying data structures and messaging formats that can be verified on any blockchain with basic smart contract capabilities. As a result, developers can rely on ERC-7824’s standard approach for off-chain interactions and dispute resolution across multiple L1 or L2 networks, preserving interoperability and reusability of state channel components in a wide range of environments. - -## Supercharging Web2/3 Applications - -The Nitro Protocol is designed to integrate effortlessly with **both Web2 and Web3 applications**. Whether operating in traditional infrastructure or leveraging blockchain ecosystems, developers can **enhance their software stack** with blockchain-grade security and efficiency without sacrificing speed or requiring major architectural changes. - -```mermaid -quadrantChart - title Distributed system scaling - x-axis Low Speed --> High Speed - y-axis Low Trust --> High Trust - quadrant-1 Network Overlay - quadrant-2 Decentralized Systems - quadrant-3 Cross-chain Systems - quadrant-4 Centralized Systems - Bitcoin: [0.20, 0.8] - L2 Chains: [0.57, 0.70] - L3 Nitro: [0.75, 0.60] - ClearSync: [0.80, 0.85] - Solana: [0.60, 0.42] - TradFi: [0.85, 0.34] - CEX: [0.70, 0.20] - Bridges: [0.24, 0.24] - Cross-chain Messaging: [0.15, 0.40] - Ethereum: [0.35, 0.78] -``` - -## Key Benefits of ERC-7824 and Nitro Protocol - -1. **Scalability Without Congestion** - By moving most interactions off-chain and leveraging **state channels**, Nitro enables **near-instant, gas-free transactions**, reducing load on the base layer blockchain. - -2. **Cost Efficiency** - Users and applications **avoid high gas fees** by settling transactions off-chain while maintaining an on-chain fallback for dispute resolution. - -3. **Chain Agnosticism & Interoperability** - ERC-7824 is designed to be **cross-chain compatible**, allowing seamless **interactions between different blockchain ecosystems**. This enables **cross-chain asset transfers, atomic swaps, and multi-chain smart contract execution**. - -4. **Security & Trustless Execution** - - Transactions occur off-chain but remain **cryptographically signed and enforceable** on-chain. - - **ForceMove** dispute resolution mechanism ensures that **malicious actors cannot manipulate state transitions**. - -5. **Modular & Flexible** - The protocol provides **plug-and-play interfaces**, allowing developers to build **custom business logic** while ensuring compatibility with the ERC-7824 framework. - -6. **Real-World Applications** - - **DeFi Scaling:** Enables off-chain transactions and batch settlements for **DEXs and lending protocols**. - - **Gaming & Metaverse:** Supports **high-speed microtransactions** for in-game economies. - - **Cross-Chain Payments:** Facilitates **low-cost, instant remittances and global transactions**. - - **Enterprise & Supply Chain:** Enhances **multi-party agreements and digital asset settlement**. - -## Summary - -ERC-7824 and the Nitro Protocol provide the **next-generation infrastructure** for decentralized applications, bridging the gap between **legacy systems and blockchain**. By combining **off-chain speed** with **on-chain security**, this framework allows developers to build high-performance, scalable solutions without being tied to a single blockchain network. diff --git a/erc7824-docs/docs/_legacy/protocol.md b/erc7824-docs/docs/_legacy/protocol.md deleted file mode 100644 index 6f638dca8..000000000 --- a/erc7824-docs/docs/_legacy/protocol.md +++ /dev/null @@ -1,430 +0,0 @@ ---- -sidebar_position: 3 -title: Protocol -description: Interfaces and nitro types for NitroApp development -keywords: [erc7824, statechannels, nitro, protocol, sdk, development, state channels, ethereum scaling, L2] -tags: - - erc7824 - - protocol - - nitro - - sdk - - docs ---- - -# Nitro Protocol - -**Nitro Protocol** is a framework for building fast, secure, and flexible state channels on Ethereum. It enables developers to keep most transactions off-chain for near-instant updates while retaining the option to settle disputes on-chain as a last resort. At the heart of Nitro are well-defined states and transition rules enforced by the on-chain Adjudicator contract. The application-specific logic (“NitroApp”) is encapsulated in an on-chain contract, which the Nitro Adjudicator references during disputes to verify that a proposed state transition is valid. - -Off-chain, developers write the code responsible for constructing and signing new states, managing state progression, and orchestrating deposits and withdrawals. This off-chain logic ensures quick state updates without paying on-chain gas fees, since new states only require on-chain submission when there is a disagreement or final payout. By following Nitro’s API and implementing these components carefully, developers can build robust applications that benefit from low latency and trust-minimized on-chain settlements. - -## Overview - -```mermaid -graph TD - subgraph Nitro Protocol - INitroAdjudicator[INitroAdjudicator] -->|Interacts with| IForceMove[IForceMove] - INitroAdjudicator -->|Manages outcomes| IMultiAssetHolder[IMultiAssetHolder] - INitroAdjudicator -->|Handles states| IStatusManager[IStatusManager] - - IForceMove -->|Implements state transitions| IForceMoveApp[IForceMoveApp] - IForceMoveApp -->|Validates states| INitroTypes[INitroTypes] - end - - subgraph NitroApps - CountingApp[CountingApp] -->|Implement| IForceMoveApp - EscrowApp[VEscrowApp] -->|Implement| IForceMoveApp - end -``` - -## 1. States & Channels - -A state channel can be thought of as a private ledger containing balances and other arbitrary data housed in a data structure called a “state.” The state of the channel is updated, committed to (via signatures), and exchanged between a fixed set of actors (participants). A state channel controls funds which are locked — typically on an L1 blockchain — and those locked funds are unlocked according to the channel’s final state when the channel finishes. - -### States - -In Nitro protocol, a state is broken up into fixed and variable parts: - -
-Solidity - -```solidity -import {ExitFormat as Outcome} from '@statechannels/exit-format/contracts/ExitFormat.sol'; - -struct FixedPart { - address[] participants; - uint48 channelNonce; - address appDefinition; - uint48 challengeDuration; -} - -struct VariablePart { - Outcome.SingleAssetExit[] outcome; // (1) - bytes appData; - uint48 turnNum; - bool isFinal; -} -``` - -
- -
-TypeScript - -```typescript -import * as ExitFormat from '@statechannels/exit-format'; -// (1) -import {Address, Bytes, Bytes32, Uint256, Uint48, Uint64} from '@statechannels/nitro-protocol'; - -export interface FixedPart { - participants: Address[]; - channelNonce: Uint64; - appDefinition: Address; - challengeDuration: Uint48; -} - -export interface VariablePart { - outcome: ExitFormat.Exit; // (2) - appData: Bytes; - turnNum: Uint48; - isFinal: boolean; -} -``` - -
- -
-Go - -```go -import ( - "github.com/statechannels/go-nitro/channel/state/outcome" - "github.com/statechannels/go-nitro/types" // (1) -) - -type ( - FixedPart struct { - Participants []types.Address - ChannelNonce uint64 - AppDefinition types.Address - ChallengeDuration uint32 - } - - VariablePart struct { - AppData types.Bytes - Outcome outcome.Exit // (2) - TurnNum uint64 - IsFinal bool - } -) -``` - -
- -1. `Bytes32`, `Bytes`, `Address`, `Uint256`, `Uint64` are aliases for hex-encoded strings. `Uint48` is aliased to a `number`. -2. This composite type is explained in more detail in the “Outcomes” section. - -Each state has: - -- **Fixed Part**: - - `participants`: The list of addresses (one per participant). - - `channelNonce`: A unique identifier so that re-using the same participants and app definition does not cause replay attacks. - - `appDefinition`: Address of the application contract (on-chain code) that enforces application-specific rules. - - `challengeDuration`: The duration (in seconds) of the challenge window in case of on-chain disputes. - -- **Variable Part**: - - `outcome`: The distribution of funds if the channel were finalized in that state. - - `appData`: Extra data interpreted by the application (e.g. game state). - - `turnNum`: A version counter for the state updates. - - `isFinal`: A boolean that triggers instant finalization when fully signed. - -#### Channel IDs - -Channels are identified by the hash of the fixed part: - -```solidity -bytes32 channelId = keccak256( - abi.encode( - fixedPart.participants, - fixedPart.channelNonce, - fixedPart.appDefinition, - fixedPart.challengeDuration - ) -); -``` - -#### State Commitments - -To commit to a state, we take the keccak256 hash of a combination of the channel ID and the variable part: - -```solidity -bytes32 stateHash = keccak256(abi.encode( - channelId, - variablePart.appData, - variablePart.outcome, - variablePart.turnNum, - variablePart.isFinal -)); -``` - -Participants sign this `stateHash` using their private key. A signature has the form `(v, r, s)` in ECDSA. - -#### Signed Variable Parts and Support Proofs - -A “signed variable part” is simply the variable part plus the signatures from participants. Submitting such bundles to the chain allows the chain to verify that a given state is “supported” off-chain. Typically, a single channel update is accompanied by a single “candidate” state plus (optionally) a sequence of older states that prove the channel progressed correctly. This bundle is called a “support proof.” - ---- - -## 2. Execution Rules - -A channel’s execution rules dictate which state updates can be considered valid and which are invalid. Once a state is considered “supported” (i.e., it has enough signatures to pass on-chain checks), it can become the channel’s final state if the channel is later finalized. - -### Core Protocol Rules - -1. **Higher Turn Number**: Among multiple states, the one with the highest turn number supersedes the rest. -2. **Instant Finalization**: If a state has `isFinal = true` and is fully signed, it can finalize the channel without needing a challenge phase. - -### Application Rules - -Each channel references an on-chain contract (the `appDefinition`) that encodes custom logic: - -```solidity -interface IForceMoveApp is INitroTypes { - function stateIsSupported( - FixedPart calldata fixedPart, - RecoveredVariablePart[] calldata proof, - RecoveredVariablePart calldata candidate - ) external view returns (bool, string memory); -} -``` - -When on-chain disputes occur, the chain calls this function to confirm that a “candidate” state is valid relative to the “proof” states. - ---- - -## 3. Outcomes - -The **outcome** of a state is the piece that dictates how funds in the channel will be disbursed once the channel is finalized. Nitro uses the [L2 exit format](https://github.com/statechannels/exit-format): - -- **Outcome**: An array of `SingleAssetExit`, each describing: - - An `asset` (e.g. the zero address for native ETH, or an ERC20 contract address), - - Optional `assetMetadata`, - - A list of **allocations**. - -Example: - -```ts -import { - Exit, - SingleAssetExit, - NullAssetMetadata, - AllocationType, -} from "@statechannels/exit-format"; - -const ethExit: SingleAssetExit = { - asset: "0x0", // native token (ETH) - assetMetadata: NullAssetMetadata, - allocations: [ - { - destination: "0x00000000000000000000000096f7123E3A80C9813eF50213ADEd0e4511CB820f", - amount: "0x05", - allocationType: AllocationType.simple, - metadata: "0x", - }, - { - destination: "0x0000000000000000000000000737369d5F8525D039038Da1EdBAC4C4f161b949", - amount: "0x05", - allocationType: AllocationType.simple, - metadata: "0x", - }, - ], -}; - -const exit = [ethExit]; -``` - -### Allocations - -Allocations specify a `destination` and an `amount`. For **direct channels**, these allocations are typically of `allocationType = 0` (simple). Each entry is the portion of the channel’s asset that the channel participant can claim once the channel is finalized on-chain. - -### Destinations - -A destination is a 32-byte value that may represent: - -- A channel ID, or -- An “external destination” (an Ethereum address left-padded with zeros). - -For **direct** channels, the relevant destinations are ordinarily external addresses corresponding to participant wallets. - ---- - -## 4. Lifecycle of a Channel - -Below we describe the lifecycle of a **direct** channel. - -### Off-Chain Lifecycle (High-Level) - -1. **Proposed**: A participant signs the prefund state (`turnNum = 0`) and shares it with others. -2. **ReadyToFund**: Once all have countersigned, the channel can safely be funded on-chain. -3. **Funded**: After the postfund state (`turnNum = 1`) is signed, the channel is recognized as funded. -4. **Running**: States with `turnNum > 1` can be signed as the channel operates. -5. **Finalized**: A state with `isFinal = true` is fully signed. - -A diagram (off-chain states): - -```mermaid -stateDiagram-v2 - [*] --> Proposed - Proposed --> ReadyToFund - ReadyToFund --> Funded - Funded --> Running - Running --> Finalized -``` - -### Funding Lifecycle (On-Chain) - -A channel is “funded” on-chain once participants’ deposits have been made to the adjudicator contract. Eventually, once the channel is finished, those funds are withdrawn to participants’ external addresses. - -```mermaid -stateDiagram-v2 -state OnChain { - state Funding { - [*]-->NotFundedOnChain - NotFundedOnChain --> FundedOnChain - FundedOnChain --> NotFundedOnChain - } -} -``` - -- **NotFundedOnChain** → channel has 0 or insufficient deposit in the adjudicator. -- **FundedOnChain** → channel has the full deposit allocated. - -### Adjudication Lifecycle (On-Chain) - -Nitro’s `NitroAdjudicator` contract tracks the channel’s status on-chain: - -1. **Open**: No challenge is in progress. -2. **Challenge**: A challenge is ongoing; participants must respond or let it expire. -3. **Finalized**: The channel has either concluded with a `conclude` transaction or the challenge timed out. - -```mermaid -stateDiagram-v2 -state OnChain { -state Adjudication { -Open -->Challenge: challenge -Open --> Open: checkpoint -Open--> Finalized: conclude -Challenge--> Challenge: challenge -Challenge--> Open: checkpoint -Challenge--> Finalized: conclude -Challenge--> Finalized': timeout -} -} -``` - ---- - -## 5. Precautions and Limits - -### Precautions - -As a participant, you should verify: - -- The number of participants is correct. -- Your own address is in `participants`. -- The `channelNonce` is unique. -- `challengeDuration` is acceptable. - -### Limits - -There are upper bounds to how large a state can be before it becomes impractical to finalize on-chain: - -- `MAX_TX_DATA_SIZE`: ~128 KB typical limit for Ethereum transaction data. -- `NITRO_MAX_GAS`: ~6M gas, a safe upper bound for some Nitro operations. -- `MAX_OUTCOME_ITEMS`: The protocol recommends limiting the number of allocation items to at most 2000 to remain under `MAX_TX_DATA_SIZE` and `NITRO_MAX_GAS`. - ---- - -## 6. Funding a Channel - -This section covers **on-chain** depositing for direct channels. - -### Fund with an On-Chain `deposit` - -When participants want to stake funds, they each perform a deposit transaction to the `NitroAdjudicator`: - -```solidity -function deposit( - address asset, - bytes32 destination, - uint256 expectedHeld, - uint256 amount -) public payable -``` - -- `asset`: zero address if depositing ETH, or an ERC20 token address if depositing tokens. -- `destination`: must be the channel’s `channelId` (not an external address). -- `expectedHeld`: ensures the holdings so far match what you expect, preventing over-funding or front-running. -- `amount`: how much is being deposited in this transaction. - -If depositing ETH, include `{value: amount}` in the transaction call. If depositing ERC20 tokens, you must first `approve` the `NitroAdjudicator` for `amount`. - -#### Outcome Priority - -It is possible for a channel to be underfunded if not all participants have deposited yet. If a channel finalizes underfunded, the distribution is paid in priority order (the topmost allocation entries get paid first). Thus, participants commonly wait until they each see the correct deposit events on-chain before signing postfund states. - ---- - -## 7. Finalizing a Channel - -A channel is **finalized** either off-chain with unanimous agreement or via an on-chain transaction (the “happy path” or the “sad path”). - -### Happy Path (Off-Chain) - -- One participant proposes a state with `isFinal = true`. -- All participants sign it. -- No on-chain action is strictly needed to *logically* finalize the channel (they can defund off-chain if no on-chain deposit was used). - -### On-Chain – Calling `conclude` - -When the channel was funded on-chain, any participant (or anyone with the finalization proof) can call: - -```solidity -function conclude( - FixedPart memory fixedPart, - SignedVariablePart memory candidate -) external -``` - -This finalizes the channel’s outcome **instantly** on-chain (no challenge period). Afterward, participants can withdraw or `transfer` funds out of the adjudicator contract. - ---- - -## 8. Defunding a Channel - -Once a channel is **finalized**, its locked funds can be released. For **direct** channels, this typically means: - -### On-Chain Defunding Using `transfer` - -The on-chain `transfer` method in the `NitroAdjudicator` contract moves funds from the channel’s escrow to participant addresses: - -```solidity -function transfer( - uint256 assetIndex, - bytes32 channelId, - bytes memory outcomeBytes, - bytes32 stateHash, - uint256[] memory indices -) public -``` - -- `assetIndex`: Which asset in the channel’s outcome you want to pay out. -- `channelId`: The identifier of the channel. -- `outcomeBytes`: The encoded outcome (list of allocations). -- `stateHash`: If finalization was done via `conclude`, you may pass `bytes32(0)`. -- `indices`: Which allocations to pay out in this transaction (`[]` means “all”). - -Once called, the relevant allocations are paid out to the listed `destination`s, and the contract’s tracked holdings are reduced accordingly. Multiple calls may be required to pay out all allocations, especially if there are many recipients. - -#### concludeAndTransferAllAssets - -The contract also offers a “batched” convenience method `concludeAndTransferAllAssets` that finalizes and transfers in a single transaction. diff --git a/erc7824-docs/docs/_legacy/resources.md b/erc7824-docs/docs/_legacy/resources.md deleted file mode 100644 index 263e6b44a..000000000 --- a/erc7824-docs/docs/_legacy/resources.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebar_position: 4 -title: Resources -description: Learn more about state channels -keywords: [erc7824, statechannels, state channels, awesome state channels, ethereum scaling, layer 2, layer 3, nitro, trading, high-speed] -tags: - - erc7824 - - links - - docs ---- - -# Resources - -### General Overviews - -- [What are State Channels? (Ethereum Foundation)](https://ethereum.org/en/developers/docs/scaling/state-channels/) -- [State Channels vs. Rollups (Vitalik Buterin)](https://vitalik.eth.limo/general/2021/01/05/rollup.html) - -### Technical Papers - -- [(2018) ForceMove: An n-party state channel protocol](https://magmo.com/force-move-games.pdf) -- [(2019) Nitro Protocol: Virtual channels](https://magmo.com/nitro-protocol.pdf) -- [(2022) Stateful Asset Transfer Protocol](https://statechannels.github.io/satp_paper/satp.pdf) -- [Nitro Protocol GitHub Repository](https://github.com/statechannels/go-nitro) - -### Videos and Tutorials - -- [State Channels Explained (YouTube)](https://www.youtube.com/watch?v=p1OTfvCbRFo) -- [Nitro Protocol Walkthrough (YouTube)](https://www.youtube.com/watch?v=Z5dcPAfIzP4) -- [Ethereum Magicians State Channel Discussion](https://ethereum-magicians.org/c/scaling/state-channels) -- [Protocol Tutorial](https://docs.statechannels.org/protocol-tutorial/0010-states-channels/) -- [State Channels Blog](https://blog.statechannels.org/) diff --git a/erc7824-docs/docs/_legacy/spec.md b/erc7824-docs/docs/_legacy/spec.md deleted file mode 100644 index 0c79e4e62..000000000 --- a/erc7824-docs/docs/_legacy/spec.md +++ /dev/null @@ -1,355 +0,0 @@ ---- -sidebar_position: 2 -title: Specification -description: Request for Comment on statechannels framework -keywords: [erc7824, ERC, Ethereum, statechannels, nitro, sdk, development, state channels, ethereum scaling, L2] -tags: - - erc7824 - - nitro - - docs ---- - -# ERC-7824 - -## Abstract - -State Channels is allowing participants to perform off-chain transactions while maintaining the security guarantees of the Ethereum blockchain. The goal is to enhance scalability and reduce transaction costs for decentralized applications. - -This standard defines a framework for implementing state channel systems, enabling efficient off-chain transaction execution and dispute resolution. It provides interfaces for managing channel states and an example implementation. - -## Motivation - -The Ethereum network faces challenges in scalability and transaction costs, making it less feasible for high-frequency interactions. State Channels provide a mechanism to perform most interactions off-chain, reserving on-chain operations for dispute resolution or final settlement. This ERC facilitate the adoption of state channels by standardizing essential interfaces. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Glossary of Terms - -- **Channel**: A construct where participants interact off-chain using signed messages. -- **Channel ID**: A unique identifier for a channel, derived from the channel's fixed part. -- **Participant**: An entity (EOA) involved in a state channel. -- **Turn Number**: A counter indicating the sequence of moves in the channel. -- **State**: A representation of the channel's condition, updated via signed messages. -- **Fixed Part**: Immutable channel parameters. -- **Variable Part**: Mutable channel parameters that evolve as states are updated. -- **ForceMove**: A procedure for resolving disputes by transitioning the state on-chain. -- **Outcome**: The final result of a channel state used for settling balances. -- **Nitro**: Historial name of the initial protocol - -### Data Structures - -#### ExitFormat - -Standard data structure for exiting an EVM based state channel. - -```solidity -/// @title ExitFormat -/// @notice Standard for encoding channel outcomes -library ExitFormat { - struct SingleAssetExit { - address asset; - AssetMetadata assetMetadata; - Allocation[] allocations; - } - - // AssetMetadata allows for different token standards - struct AssetMetadata { - AssetType assetType; - bytes metadata; - } - - enum AssetType {Default, ERC721, ERC1155, Qualified} - - enum AllocationType {simple, withdrawHelper, guarantee} - - struct Allocation { - bytes32 destination; - uint256 amount; - uint8 allocationType; - bytes metadata; - } -} -``` - -#### NitroTypes - -The `FixedPart`, `VariablePart` compose a "state". The state of the channel is updated, committed to and exchanged between a fixed set of participants. - -```solidity -/// @title NitroTypes -/// @notice Defines the core data structures used in state channels. -interface INitroTypes { - struct FixedPart { - address[] participants; - uint64 channelNonce; // This is a unique number used to differentiate channels - address appDefinition; // This is an Ethereum address where a ForceMoveApp has been deployed - uint48 challengeDuration; // This is duration in seconds of the challenge-response window - } - - struct VariablePart { - Outcome.SingleAssetExit[] outcome; - bytes appData; - uint48 turnNum; - bool isFinal; // This is a boolean flag which allows the channel to be finalized "instantly" - } - - struct SignedVariablePart { - VariablePart variablePart; - Signature[] sigs; - } - - struct RecoveredVariablePart { - VariablePart variablePart; - uint256 signedBy; // bitmask - } -} -``` - -#### Channel ID - -A `channelId` is derived using: - -```solidity -bytes32 channelId = keccak256( - abi.encode( - fixedPart.participants, - fixedPart.channelNonce, - fixedPart.appDefinition, - fixedPart.challengeDuration - ) -); -``` - -## State commitments - -To commit to a state, a hash is formed as follows: - -```solidity -bytes32 stateHash = keccak256(abi.encode( - channelId, - variablePart.appData, - variablePart.outcome, - variablePart.turnNum, - variablePart.isFinal -)); -``` - -### Interfaces - -#### IForceMoveApp - -Define the state machine of a ForceMove state channel protocol - -```solidity -/// @title IForceMoveApp -/// @notice Interface for implementing protocol rules in state channels -interface IForceMoveApp is INitroTypes { - // @notice Encodes application-specific rules for a particular ForceMove-compliant state channel. Must revert or return false when invalid support proof and a candidate are supplied. - // @dev Depending on the application, it might be desirable to narrow the state mutability of an implementation to 'pure' to make security analysis easier. - // @param fixedPart Fixed part of the state channel. - // @param proof Array of recovered variable parts which constitutes a support proof for the candidate. May be omitted when `candidate` constitutes a support proof itself. - // @param candidate Recovered variable part the proof was supplied for. Also may constitute a support proof itself. - function stateIsSupported( - FixedPart calldata fixedPart, - RecoveredVariablePart[] calldata proof, - RecoveredVariablePart calldata candidate - ) external view returns (bool, string memory); -} -``` - -#### IForceMove - -The IForceMove interface defines the interface that an implementation of ForceMove should implement. - -```solidity -interface IForceMove is INitroTypes { - /** - * @notice Registers a challenge against a state channel. A challenge will either prompt another participant into clearing the challenge (via one of the other methods), or cause the channel to finalize at a specific time. - * @dev Registers a challenge against a state channel. A challenge will either prompt another participant into clearing the challenge (via one of the other methods), or cause the channel to finalize at a specific time. - * @param fixedPart Data describing properties of the state channel that do not change with state updates. - * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof. - * @param candidate A candidate state (along with signatures) which is being claimed to be supported. - * @param challengerSig The signature of a participant on the keccak256 of the abi.encode of (supportedStateHash, 'forceMove'). - */ - function challenge( - FixedPart memory fixedPart, - SignedVariablePart[] memory proof, - SignedVariablePart memory candidate, - Signature memory challengerSig - ) external; - - /** - * @notice Overwrites the `turnNumRecord` stored against a channel by providing a candidate with higher turn number. - * @dev Overwrites the `turnNumRecord` stored against a channel by providing a candidate with higher turn number. - * @param fixedPart Data describing properties of the state channel that do not change with state updates. - * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof. - * @param candidate A candidate state (along with signatures) which is being claimed to be supported. - */ - function checkpoint( - FixedPart memory fixedPart, - SignedVariablePart[] memory proof, - SignedVariablePart memory candidate - ) external; - - /** - * @notice Finalizes a channel according to the given candidate. External wrapper for _conclude. - * @dev Finalizes a channel according to the given candidate. External wrapper for _conclude. - * @param fixedPart Data describing properties of the state channel that do not change with state updates. - * @param candidate A candidate state (along with signatures) to change to. - */ - function conclude(FixedPart memory fixedPart, SignedVariablePart memory candidate) external; - - // events - - /** - * @dev Indicates that a challenge has been registered against `channelId`. - * @param channelId Unique identifier for a state channel. - * @param finalizesAt The unix timestamp when `channelId` will finalize. - * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof. - * @param candidate A candidate state (along with signatures) which is being claimed to be supported. - */ - event ChallengeRegistered( - bytes32 indexed channelId, - uint48 finalizesAt, - SignedVariablePart[] proof, - SignedVariablePart candidate - ); - - /** - * @dev Indicates that a challenge, previously registered against `channelId`, has been cleared. - * @param channelId Unique identifier for a state channel. - * @param newTurnNumRecord A turnNum that (the adjudicator knows) is supported by a signature from each participant. - */ - event ChallengeCleared(bytes32 indexed channelId, uint48 newTurnNumRecord); - - /** - * @dev Indicates that an on-chain channel data was successfully updated and now has `newTurnNumRecord` as the latest turn number. - * @param channelId Unique identifier for a state channel. - * @param newTurnNumRecord A latest turnNum that (the adjudicator knows) is supported by adhering to channel application rules. - */ - event Checkpointed(bytes32 indexed channelId, uint48 newTurnNumRecord); - - /** - * @dev Indicates that a challenge has been registered against `channelId`. - * @param channelId Unique identifier for a state channel. - * @param finalizesAt The unix timestamp when `channelId` finalized. - */ - event Concluded(bytes32 indexed channelId, uint48 finalizesAt); -} -``` - -### Workflows - -1. **Opening a Channel**: Participants agree on initial states and open a channel on-chain. -2. **Funding a Channel**: Participants transfer the agreed amount of funds. -3. **Off-Chain Interaction**: Participants exchange signed state updates off-chain. -4. **Dispute Resolution**: If a disagreement arises, a participant can force a state on-chain. -5. **Finalization**: Upon agreement or after a timeout, the channel is finalized, and the outcome is settled. - -#### Example Application - -The CountingApp contract complies with the ForceMoveApp interface and strict turn taking logic and allows only for a simple counter to be incremented. - -```solidity -contract CountingApp is IForceMoveApp { - struct CountingAppData { - uint256 counter; - } - - /** - * @notice Decodes the appData. - * @dev Decodes the appData. - * @param appDataBytes The abi.encode of a CountingAppData struct describing the application-specific data. - * @return A CountingAppData struct containing the application-specific data. - */ - function appData(bytes memory appDataBytes) internal pure returns (CountingAppData memory) { - return abi.decode(appDataBytes, (CountingAppData)); - } - - /** - * @notice Encodes application-specific rules for a particular ForceMove-compliant state channel. - * @dev Encodes application-specific rules for a particular ForceMove-compliant state channel. - * @param fixedPart Fixed part of the state channel. - * @param proof Array of recovered variable parts which constitutes a support proof for the candidate. - * @param candidate Recovered variable part the proof was supplied for. - */ - function stateIsSupported( - FixedPart calldata fixedPart, - RecoveredVariablePart[] calldata proof, - RecoveredVariablePart calldata candidate - ) external pure override returns (bool, string memory) { - StrictTurnTaking.requireValidTurnTaking(fixedPart, proof, candidate); - - require(proof.length != 0, '|proof| = 0'); - - // validate the proof - for (uint256 i = 1; i < proof.length; i++) { - _requireIncrementedCounter(proof[i], proof[i - 1]); - _requireEqualOutcomes(proof[i], proof[i - 1]); - } - - _requireIncrementedCounter(candidate, proof[proof.length - 1]); - _requireEqualOutcomes(candidate, proof[proof.length - 1]); - - return (true, ''); - } - - /** - * @notice Checks that counter encoded in first variable part equals an incremented counter in second variable part. - * @dev Checks that counter encoded in first variable part equals an incremented counter in second variable part. - * @param b RecoveredVariablePart with incremented counter. - * @param a RecoveredVariablePart with counter before incrementing. - */ - function _requireIncrementedCounter( - RecoveredVariablePart memory b, - RecoveredVariablePart memory a - ) internal pure { - require( - appData(b.variablePart.appData).counter == appData(a.variablePart.appData).counter + 1, - 'Counter must be incremented' - ); - } - - /** - * @notice Checks that supplied signed variable parts contain the same outcome. - * @dev Checks that supplied signed variable parts contain the same outcome. - * @param a First RecoveredVariablePart. - * @param b Second RecoveredVariablePart. - */ - function _requireEqualOutcomes( - RecoveredVariablePart memory a, - RecoveredVariablePart memory b - ) internal pure { - require( - Outcome.exitsEqual(a.variablePart.outcome, b.variablePart.outcome), - 'Outcome must not change' - ); - } -} -``` - -## Rationale - -State channels offer significant scalability improvements by minimizing on-chain transactions. This standard provides clear interfaces to ensure interoperability and efficiency while retaining flexibility for custom protocol rules. - -**Scalability**: State channels alleviate these concerns by moving most interactions off-chain, while the blockchain serves as a settlement layer. This construction enable high frequency applications. - -**Modular**: Interfaces act as contracts that specify what functions must be implemented without dictating how, allowing developers to create custom implementations suited to specific use cases while maintaining compatibility with the framework. - -**Interoperability**: Enable cross-chain interactions, participants can be using two differents chains for opening their channels and perform for example cross-chain atomic swaps. - -**Security**: The standard would enable to build an audited framework of primitive which is flexible to accommodate a large number of use cases. ERC-7824 is designed to accommodate this diversity by normalizing common protocol patterns. - -## Backwards Compatibility - -No backward compatibility issues found. This ERC is designed to coexist with existing standards and can integrate with [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) and [ERC-4337](https://www.erc4337.io/) - -## Security Considerations - -This ERC is agnostic of the protocol rules that must be implemented using IForceMoveApp. While the smart-contract framework is a simple set of convention, much of the security risk is moved off-chain. Protocols using State channels must perform security audit of their client and server backend implementation. - -## Copyright - -Copyright and related rights waived via [CC0](https://github.com/ethereum/ERCs/blob/master/LICENSE.md). diff --git a/erc7824-docs/docs/erc-7824.md b/erc7824-docs/docs/erc-7824.md deleted file mode 100644 index f58d4542d..000000000 --- a/erc7824-docs/docs/erc-7824.md +++ /dev/null @@ -1,498 +0,0 @@ ---- -sidebar_position: 2 -title: ERC-7824 -description: Interfaces and data types for cross-chain stateful asset transfer -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, javascript, typescript, sdk] ---- -# ERC-7824 - -## Abstract - -Nitrolite is a lightweight, efficient state channel framework for Ethereum and other EVM-compatible blockchains, enabling off-chain interactions while maintaining on-chain security guarantees. The framework allows participants to perform instant transactions with reduced gas costs while preserving the security of the underlying blockchain. - -This standard defines a framework for implementing state channel systems through the Nitrolite protocol, providing interfaces for channel creation, state management, dispute resolution, and fund custody. It enables high-throughput applications with minimal on-chain footprint. - -## Motivation - -The Ethereum network faces challenges in scalability and transaction costs, making it less feasible for high-frequency interactions. The Nitrolite framework addresses these challenges by providing a lightweight state channel solution that enables: - -- **Instant Finality**: Transactions settle immediately between parties -- **Reduced Gas Costs**: Most interactions happen off-chain, with minimal on-chain footprint -- **High Throughput**: Support for thousands of transactions per second -- **Security Guarantees**: Same security as on-chain, with cryptographic proofs -- **Chain Agnostic**: Works with any EVM-compatible blockchain - -This ERC standardizes the Nitrolite protocol interfaces to facilitate widespread adoption of state channels. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Glossary of Terms - -- **Channel**: A relationship between participants that allows off-chain state updates with on-chain settlement -- **Channel ID**: A unique identifier derived from channel configuration (participants, adjudicator, challenge period, nonce) -- **Participant**: An entity (EOA) involved in a state channel -- **State**: A signed data structure containing version, allocations, and application data -- **Allocation**: Specification of token distribution to destinations -- **Adjudicator**: Contract that validates state transitions according to application rules -- **Challenge Period**: Duration for dispute resolution before finalization -- **Status**: Channel lifecycle stage (VOID, INITIAL, ACTIVE, DISPUTE, FINAL) -- **State Intent**: Purpose of a state (OPERATE, INITIALIZE, RESIZE, FINALIZE) -- **Custody**: On-chain contract holding locked funds for channels -- **Checkpoint**: Recording a valid state on-chain without closing the channel - -### Data Structures - -The Nitrolite protocol defines the following core data structures: - -#### Basic Types - -```solidity -struct Amount { - address token; // ERC-20 token address (address(0) for native tokens) - uint256 amount; // Token amount -} - -struct Allocation { - address destination; // Where funds are sent on channel closure - address token; // ERC-20 token contract address (address(0) for native tokens) - uint256 amount; // Token amount allocated -} -``` - -#### Channel Configuration - -```solidity -struct Channel { - address[] participants; // List of participants in the channel - address adjudicator; // Address of the contract that validates state transitions - uint64 challenge; // Duration in seconds for dispute resolution period - uint64 nonce; // Unique per channel with same participants and adjudicator -} -``` - -#### State Structure - -```solidity -struct State { - StateIntent intent; // Intent of the state - uint256 version; // State version incremental number to compare most recent - bytes data; // Application data encoded, decoded by the adjudicator - Allocation[] allocations; // Asset allocation and destination for each participant - bytes[] sigs; // stateHash signatures from participants -} - -enum StateIntent { - OPERATE, // Normal operation state - INITIALIZE, // Initial funding state - RESIZE, // Resize allocations state - FINALIZE // Final closing state -} -``` - -#### Channel Status - -```solidity -enum Status { - VOID, // Channel was not created, State.version must be 0 - INITIAL, // Channel is created and in funding process, State.version must be 0 - ACTIVE, // Channel fully funded and operational, State.version > 0 - DISPUTE, // Challenge period is active - FINAL // Final state, channel can be closed -} -``` - -#### Channel ID - -The channel ID is computed as: - -```solidity -bytes32 channelId = keccak256( - abi.encode( - channel.participants, - channel.adjudicator, - channel.challenge, - channel.nonce - ) -); -``` - -#### State Hash - -For signature verification, the state hash is computed as: - -```solidity -bytes32 stateHash = keccak256( - abi.encode( - channelId, - state.intent, - state.version, - state.data, - state.allocations - ) -); -``` - -Note: The smart contract supports all popular signature formats, specifically: raw ECDSA, EIP-191, EIP-712, EIP-1271, and EIP-6492. - -### Interfaces - -#### IAdjudicator - -Defines the interface for contracts that validate state transitions according to application-specific rules: - -```solidity -interface IAdjudicator { - /** - * @notice Validates a candidate state based on application-specific rules - * @dev Used to determine if a state is valid during challenges or checkpoints - * @param chan The channel configuration - * @param candidate The proposed state to be validated - * @param proofs Array of previous states that provide context for validation - * @return valid True if the candidate state is valid according to application rules - */ - function adjudicate( - Channel calldata chan, - State calldata candidate, - State[] calldata proofs - ) external returns (bool valid); -} -``` - -#### IComparable - -Interface for determining the ordering between states: - -```solidity -interface IComparable { - /** - * @notice Compares two states to determine their relative ordering - * @dev Returns: -1 if candidate < previous, 0 if equal, 1 if candidate > previous - * @param candidate The state being evaluated - * @param previous The reference state to compare against - * @return result The comparison result - */ - function compare( - State calldata candidate, - State calldata previous - ) external view returns (int8 result); -} -``` - -#### IChannel - -The main state channel interface that manages the channel lifecycle: - -```solidity -interface IChannel { - // Events - event Created(bytes32 indexed channelId, Channel channel, State initial); - event Joined(bytes32 indexed channelId, uint256 index); - event Opened(bytes32 indexed channelId); - event Challenged(bytes32 indexed channelId, uint256 expiration); - event Checkpointed(bytes32 indexed channelId); - event Resized(bytes32 indexed channelId, int256[] deltaAllocations); - event Closed(bytes32 indexed channelId); - - /** - * @notice Creates a new channel and initializes funding - * @dev Creator must sign the funding state with StateIntent.INITIALIZE. - * If both participants sign the initial state, channel opens immediately in ACTIVE status. - * If only creator signs, channel enters INITIAL status awaiting join(). - * @param ch Channel configuration - * @param initial Initial state with StateIntent.INITIALIZE and expected allocations - * @return channelId Unique identifier for the created channel - */ - function create(Channel calldata ch, State calldata initial) - external returns (bytes32 channelId); - - /** - * @notice Allows a participant to join a channel by signing the funding state - * @dev Only needed when channel was created with single signature. - * Participant must provide signature on the same funding state. - * @param channelId Unique identifier for the channel - * @param index Index of the participant in the channel's participants array - * @param sig Signature of the participant on the funding state - * @return channelId Unique identifier for the joined channel - */ - function join(bytes32 channelId, uint256 index, bytes calldata sig) - external returns (bytes32); - - /** - * @notice Finalizes a channel with a mutually signed closing state - * @dev Requires all participants' signatures on a state with StateIntent.FINALIZE - * @param channelId Unique identifier for the channel - * @param candidate The latest known valid state to be finalized - * @param proofs Additional states required by the adjudicator - */ - function close( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs - ) external; - - /** - * @notice Resizes channel allocations with participant agreement - * @dev Used for adjusting channel allocations without withdrawing funds - * @param channelId Unique identifier for the channel - * @param candidate The state with new allocations - * @param proofs Supporting states for validation - */ - function resize( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs - ) external; - - /** - * @notice Initiates or updates a challenge with a signed state - * @dev Starts a challenge period during which participants can respond - * @param channelId Unique identifier for the channel - * @param candidate The state being submitted as the latest valid state - * @param proofs Additional states required by the adjudicator - * @param challengerSig Signature of the challenger on the candidate state. Must be signed by one of the participants - */ - function challenge( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs, - bytes calldata challengerSig - ) external; - - /** - * @notice Records a valid state on-chain without initiating a challenge - * @dev Used to establish on-chain proof of the latest state - * @param channelId Unique identifier for the channel - * @param candidate The state to checkpoint - * @param proofs Additional states required by the adjudicator - */ - function checkpoint( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs - ) external; -} -``` - -#### IDeposit - -Interface for managing token deposits and withdrawals: - -```solidity -interface IDeposit { - /** - * @notice Deposits tokens into the contract - * @dev For native tokens, the value should be sent with the transaction - * @param wallet Address of the account whose ledger is changed - * @param token Token address (use address(0) for native tokens) - * @param amount Amount of tokens to deposit - */ - function deposit(address wallet, address token, uint256 amount) external payable; - - /** - * @notice Withdraws tokens from the contract - * @dev Can only withdraw available (not locked in channels) funds - * @param wallet Address of the account whose ledger is changed - * @param token Token address (use address(0) for native tokens) - * @param amount Amount of tokens to withdraw - */ - function withdraw(address wallet, address token, uint256 amount) external; -} -``` - -### Channel Lifecycle - -1. **Creation**: Creator constructs channel config and signs initial state with `StateIntent.INITIALIZE`. The channel can be opened immediately in one transaction if both participants provide signatures over the initial state, with both participants' funds being deducted from their available balances. -2. **Active**: Once fully funded (either through single-transaction creation or separate join), the channel transitions to active state for off-chain operation -3. **Off-chain Updates**: Participants exchange and sign state updates according to application logic -4. **Resolution**: - - **Cooperative Close**: All parties sign a final state with `StateIntent.FINALIZE` - - **Challenge-Response**: Participant can post a state on-chain and initiate challenge period - - **Checkpoint**: Record valid state on-chain without closing for future dispute resolution - - **Resize**: Adjust allocations by agreement without closing the channel - -#### Two-Phase Opening (Legacy) - -For scenarios requiring separate funding from external accounts: - -1. **Creation**: Creator calls `create()` with single signature, channel enters INITIAL status -2. **Joining**: Second participant calls `join()` with their signature, transitioning channel to ACTIVE status - -### Example Implementation: Remittance Adjudicator - -The Remittance adjudicator validates payment transfers between participants: - -```solidity -contract Remittance is IAdjudicator, IComparable { - struct Intent { - uint8 payer; // Index of the paying participant - Amount transfer; // Amount and token being transferred - } - - /** - * @notice Validates a payment state transition - * @dev Checks that the payer has signed and allocations are correct - */ - function adjudicate( - Channel calldata chan, - State calldata candidate, - State[] calldata proofs - ) external returns (bool valid) { - // Decode the payment intent - Intent memory intent = abi.decode(candidate.data, (Intent)); - - // For first state (version 1), need funding state as proof - if (candidate.version == 1) { - require(proofs.length >= 1, "Missing funding state"); - State memory funding = proofs[0]; - require(funding.intent == StateIntent.INITIALIZE, "Invalid funding state"); - } - - // For subsequent states, need previous state - if (candidate.version > 1) { - require(proofs.length >= 2, "Missing previous state"); - State memory previous = proofs[1]; - - // Verify state transition - require(candidate.version == previous.version + 1, "Invalid version"); - - // Verify allocations match the intent - require( - candidate.allocations[intent.payer].amount == - previous.allocations[intent.payer].amount - intent.transfer.amount, - "Invalid payer allocation" - ); - - uint8 payee = intent.payer == 0 ? 1 : 0; - require( - candidate.allocations[payee].amount == - previous.allocations[payee].amount + intent.transfer.amount, - "Invalid payee allocation" - ); - } - - // Verify payer has signed - require(candidate.sigs[intent.payer].v != 0, "Missing payer signature"); - - return true; - } - - /** - * @notice Compares states by version number - */ - function compare( - State calldata candidate, - State calldata previous - ) external view returns (int8 result) { - if (candidate.version < previous.version) return -1; - if (candidate.version > previous.version) return 1; - return 0; - } -} -``` - -### Example Usage - -```solidity -// Create a channel between Alice and Bob -Channel memory channel = Channel({ - participants: [alice, bob], - adjudicator: address(remittanceAdjudicator), - challenge: 3600, // 1 hour challenge period - nonce: 1 -}); - -// Create initial funding state -State memory fundingState = State({ - intent: StateIntent.INITIALIZE, - version: 0, - data: "", - allocations: [ - Allocation(alice, tokenAddress, 100 ether), - Allocation(bob, tokenAddress, 50 ether) - ], - sigs: [aliceSignature, bobSignature] -}); - -// Alice creates and funds the channel - opens immediately since both signatures provided -bytes32 channelId = custody.create(channel, fundingState); - -// Alternative: Single-signature creation followed by join -State memory fundingStatePartial = State({ - intent: StateIntent.INITIALIZE, - version: 0, - data: "", - allocations: [ - Allocation(alice, tokenAddress, 100 ether), - Allocation(bob, tokenAddress, 50 ether) - ], - sigs: [aliceSignature] // Only Alice's signature -}); -bytes32 channelId = custody.create(channel, fundingStatePartial); -custody.join(channelId, 1, bobSignature); - -// Off-chain: Alice pays Bob 10 tokens -Intent memory paymentIntent = Intent({ - payer: 0, // Alice is payer - transfer: Amount(tokenAddress, 10 ether) -}); - -State memory paymentState = State({ - intent: StateIntent.OPERATE, - version: 1, - data: abi.encode(paymentIntent), - allocations: [ - Allocation(alice, tokenAddress, 90 ether), - Allocation(bob, tokenAddress, 60 ether) - ], - sigs: [aliceSignature, bobSignature] -}); - -// Either party can checkpoint this state on-chain -custody.checkpoint(channelId, paymentState, [fundingState]); -``` - -## Rationale - -The Nitrolite framework addresses critical blockchain scalability challenges through a lightweight state channel design. This standard provides clear interfaces to ensure interoperability while maintaining flexibility for diverse applications. - -**Efficiency**: Nitrolite minimizes on-chain footprint by requiring only essential operations (create, join, close) to occur on-chain, with all application logic executed off-chain. This enables instant finality and dramatically reduces gas costs. - -**Modularity**: The separation of concerns between the custody contract (IChannel), state validation (IAdjudicator), and fund management (IDeposit) allows developers to implement custom application logic while leveraging battle-tested infrastructure. - -**Flexibility**: The adjudicator pattern supports any application logic - from simple payments to complex multi-step protocols. The state intent system (INITIALIZE, OPERATE, RESIZE, FINALIZE) provides clear semantics for different operation types. - -**Security**: The challenge-response mechanism ensures that participants can always recover their funds by posting the latest valid state on-chain. The checkpoint feature allows proactive security by recording states without closing channels. - -**Chain Agnostic**: The protocol design avoids chain-specific features, enabling deployment across any EVM-compatible blockchain and facilitating cross-chain applications through the clearnode architecture. - -## Backwards Compatibility - -No backward compatibility issues found. This ERC is designed to coexist with existing standards and can integrate with [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) and [ERC-4337](https://www.erc4337.io/) - -## Security Considerations - -### On-Chain Security - -- **Signature Verification**: All state transitions require valid signatures from participants. The protocol supports signatures of all popular formats, including EIP-191 and EIP-712. -- **Challenge Period**: The configurable challenge duration provides time for honest participants to respond to invalid states. -- **Adjudicator Validation**: Custom adjudicators must be carefully audited as they control state transition rules. -- **Reentrancy Protection**: Implementation should follow checks-effects-interactions pattern, especially in fund distribution. - -### Off-Chain Security - -- **State Storage**: Participants must securely store all signed states as they may need them for disputes. -- **Signature Security**: Private keys used for state signing must be protected as compromise allows unauthorized state transitions. -- **Availability**: Participants must monitor the chain for challenges during the challenge period. -- **Front-running**: Challenge transactions may be front-run; implementations should consider commit-reveal schemes if needed. - -### Implementation Considerations - -- The custody contract strictly enforces 2-participant channels in the reference implementation. -- Adjudicators should validate all state transitions according to application rules. -- Proper nonce management prevents replay attacks across different channels. - -## Copyright - -Copyright and related rights waived via [CC0](https://github.com/ethereum/ERCs/blob/master/LICENSE.md). diff --git a/erc7824-docs/docs/guides/index.md b/erc7824-docs/docs/guides/index.md deleted file mode 100644 index aab9eff73..000000000 --- a/erc7824-docs/docs/guides/index.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -sidebar_position: 1 -title: Guides -description: Comprehensive guides for working with Nitrolite and the ERC-7824 standard -keywords: [guides, tutorials, migration, best practices, nitrolite, erc7824] ---- - -import { Card, CardGrid } from '@site/src/components/Card'; - -# Guides - -Welcome to the Nitrolite guides section. These comprehensive resources will help you understand and implement various aspects of the ERC-7824 protocol and Nitrolite SDK. - - - - - -## Guide Categories - -### Getting Started -For developers new to Nitrolite, we recommend starting with our [Quick Start](/quick_start) guide, which covers the fundamental concepts and basic implementation. - -### Migration & Upgrades -The [Migration Guide](migration-guide) provides detailed information about breaking changes between versions and how to update your code accordingly. This is essential reading when upgrading your Nitrolite dependencies. - -### Coming Soon - -We're continuously expanding our documentation. Future guides will include: - -- **Best Practices**: Optimization techniques and design patterns for state channel applications -- **Security Guide**: Security considerations and audit recommendations -- **Performance Tuning**: Maximizing throughput and minimizing latency -- **Integration Examples**: Real-world examples of integrating Nitrolite with popular frameworks -- **Troubleshooting**: Common issues and their solutions - -## Contributing - -Have a guide idea or found an issue? We welcome contributions! Please visit our [GitHub repository](https://github.com/erc7824/nitrolite) to submit suggestions or improvements. \ No newline at end of file diff --git a/erc7824-docs/docs/guides/migration-guide.md b/erc7824-docs/docs/guides/migration-guide.md deleted file mode 100644 index 39c8bf862..000000000 --- a/erc7824-docs/docs/guides/migration-guide.md +++ /dev/null @@ -1,930 +0,0 @@ ---- -sidebar_position: 2 -title: Migration Guide -description: Guide to migrate to newer versions of Nitrolite -keywords: [migration, upgrade, breaking changes, nitrolite, erc7824] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Migration Guide - -If you are coming from an earlier version of Nitrolite, you will need to account for the following breaking changes. - -## 0.5.x Breaking changes - -The 0.5.x release includes fundamental protocol changes affecting session keys, channel operations, state signatures, and channel resize rules. The main objective of these changes is to enhance security, and provide better experience for developers and users by ability to limit allowances for specific applications. - -**Not ready to migrate?** Unfortunately, at this time Yellow Network does not provide ClearNodes running the previous version of the protocol, so you will need to migrate to the latest version to continue using the Network. - -### Protocol Changes - -These protocol-level changes affect all implementations and integrations with the Yellow Network. - -#### Session Keys: Applications, Allowances, and Expiration - -Session keys now have enhanced properties that define their access levels and capabilities: - -- **Application field**: Determines the scope of session key permissions. Setting this to an application name (e.g., "My Trading App") grants application-scoped access with enforced allowances. Setting it to "clearnode" grants root access equivalent to the wallet itself. - -- **Allowances field**: Defines spending limits for application-scoped session keys. These limits are tracked cumulatively across all operations and are enforced by the protocol. - -- **Expires_at field**: Uses a bigint timestamp (seconds since epoch). Once expired, session keys are permanently frozen and cannot be reactivated. This is particularly critical for root access keys (application set to "clearnode") - if they expire, you lose the ability to perform channel operations. - -#### Channel Creation: Separate Create and Fund Steps - -Clearnode no longer supports creating channels with an initial deposit. All channels must be created with zero balance and funded separately through a resize operation. This two-step process ensures cleaner state management and prevents edge cases in channel initialization. - -#### State Signatures: Wallet vs Session Key Signing - -A fundamental change in how channel states are signed: - -- **Channels created before v0.5.0**: The participant address is the session key, and all states must be signed by that session key. - -- **Channels created after v0.5.0**: The participant address is the wallet address, and all states must be signed by the wallet. - -This change improves security and aligns with standard practices, but requires careful handling during the transition period. - -#### Resize Operations: Strict Channel Balance Rules - -The protocol now enforces strict rules about channel balances and their impact on other operations: - -- **Blocked operations**: Users with any channel containing non-zero amounts cannot perform transfers, submit app states with deposit intent, or create app sessions with non-zero allocations. - -- **Resizing state**: After a resize request, channels enter a "resizing" state with locked funds until the on-chain transaction is confirmed. If a channel remains stuck in this state for an extended period, the recommended action is to close the channel and create a new one. - -- **Allocate amount semantics**: The resize operation uses `allocate_amount` where negative values withdraw from the channel to unified balance, and positive values deposit to the channel. - -:::warning -**Legacy channel migration**: Users with existing channels containing non-zero amounts must either resize them to zero (by providing "resize_amount" as 0 and "allocate_amount" as your **negative** on-chain balance) or close them to enable full protocol functionality. If you are unsure how to adjust resize parameters, the safe option is to close the old on-chain channel entirely, and open a new one. -::: - -#### Non-Zero Channel Allocations: Operation Restrictions - -The following operations will return errors if the user has any channel with non-zero amount: - -- **Transfer**: Returns error code indicating blocked due to non-zero channel balance -- **Submit App State** (with deposit intent): Rejected if attempting to deposit -- **Create App Session** (with allocations): Rejected if attempting to allocate - -The returned error has the following format: `operation denied: non-zero allocation in channel(s) detected owned by wallet
"` - -### Nitrolite SDK - -You should definitely read this section if you are using the Nitrolite SDK. - -#### Update Authentication - -Implementing the new session key protocol changes: - - - - - ```typescript - const authRequest = { - address: '0x...', - session_key: '0x...', - application: 'My Trading App', // Application name for confined access - allowances: [ - { asset: 'usdc', amount: '1000.0' }, - { asset: 'eth', amount: '0.5' } - ], - scope: 'app.create', - expires_at: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60) // 7 days - }; - ``` - - - - - ```typescript - const authRequest = { - address: '0x...', - session_key: '0x...', - application: 'clearnode', // Special value for root access - allowances: [], // Not enforced for root access - scope: 'app.create', - expires_at: BigInt(Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60) // Long expiration recommended - }; - ``` - - - - -**Important considerations:** -- Root access keys (application: "clearnode") cannot perform channel operations after expiration -- Plan expiration times based on your operational needs -- Application-scoped keys track cumulative spending against allowances - -#### Migrate Channel Creation - -Channels must now be created with zero initial deposit and funded separately via the `resizeChannel` method: - -```typescript -const { channelId } = await client.createChannel({ - chain_id: 1, - token: tokenAddress, - // remove-next-line - amount: BigInt(1000000), // Initial deposit - // remove-next-line - session_key: '0x...' // Optional -}); - -// add-start -// Step 2: Fund the channel separately -await client.resizeChannel({ - channel_id: channelId, - amount: BigInt(1000000), -}); -// add-end -``` - -#### Resize correctly - -Channel resizing must be negotiated with the ClearNode through WebSocket. Use `resize_amount` and `allocate_amount` with correct sign convention (`resize_amount = -allocate_amount`) and help users with non-zero channel balances migrate by resizing to zero or reopening channels. - -Channel resize can be requested as follows: - -```typescript -const resizeMessage = await createResizeChannelMessage(messageSigner, { - channel_id: channelId, - resize_amount: BigInt(50), // Positive = deposit to channel, negative = withdraw from channel to custody ledger - allocate_amount: BigInt(-50), // Negative = deposit to unified balance, negative = withdraw from unified balance to channel - funds_destination: walletAddress, -}); - -const resizeResponse = {}; // send the message and wait for Clearnode's response - -const { params: resizeResponseParams } = parseResizeChannelResponse(resizeResponse); -const resizeParams = { - resizeState: { - channelId, - ...resizeResponseParams.state, - serverSignature: resizeResponseParams.serverSignature, - data: resizeResponseParams.state.stateData as Hex, - version: BigInt(resizeResponseParams.state.version), - }, - // `previousState` is either initial or previous resizing state, depending on which has higher version number - // can be obtained with `await (client.getChannelData(channelId)).lastValidState` - proofStates: [previousState], -} - -const {txHash} = await client.resizeChannel(resizeParams); -``` - -Here is how you can migrate your channels: - -```typescript -// Check and migrate channels with non-zero amounts -const channels = await client.getOpenChannels(); - -for (const channel of channels) { - if (channel.amount > 0) { - // Must empty channel to enable transfers/app operations - const resizeMessage = await createResizeChannelMessage(messageSigner, { - channel_id: channel.channelId, - resize_amount: BigInt(0), - allocate_amount: -BigInt(channel.amount), - funds_destination: walletAddress, - }); - - // perform the resize as shown above - } -} -``` - - -**Critical:** Operations blocked when any channel has non-zero amount: -- Off-chain transfers -- App state submissions with deposit intent -- Creating app sessions with allocations - -#### Test State Signatures - -If you plan to work with on-chain channels opened PRIOR to v0.5.0, then on NitroliteClient initialization the `stateSigner` you specify must be based on a Session Key used in the channel as participant. Even if this session key is or will expire, you still need to provide a `stateSigner` based on it. - -On the other hand, if you plan to work with channels created SINCE v0.5.0, you can specify the `stateSigner` based on the `walletClient` you have specified. - -#### Manage Session Keys - -New methods have been added for comprehensive session key management, including retrieval and revocation. - -```typescript -// Get all active session keys -const sessionKeys = await client.getSessionKeys(); - -// Revoke a specific session key -await client.revokeSessionKey({ - session_key: '0x...' -}); - -// Session key data structure -interface RPCSessionKey { - id: string; - sessionKey: Address; - application: string; - allowances: RPCAllowanceUsage[]; // Includes usage tracking - scope: string; - expiresAt: bigint; - createdAt: bigint; -} -``` - -#### EIP-712 Signatures: String-based Amounts - -EIP-712 signature types now use string values for amounts instead of numeric types to support better precision with decimal values. - -```typescript -const types = { - Allowance: [ - { name: 'asset', type: 'string' }, - // remove-next-line - { name: 'amount', type: 'uint256' }, - // add-next-line - { name: 'amount', type: 'string' }, - ] -}; -``` - -### ClearNode API - -You should read this section only if you are using the ClearNode API directly. - -#### Update Authentication - -Use the new session key parameters with proper `application`, `allowances`, and `expires_at` fields: - - - - - ```json - { - "req": [1, "auth_request", { - "address": "0x1234567890abcdef...", - "session_key": "0x9876543210fedcba...", - "application": "My Trading App", - "allowances": [ - { "asset": "usdc", "amount": "1000.0" }, - { "asset": "eth", "amount": "0.5" } - ], - "scope": "app.create", - "expires_at": 1719123456789 - }, 1619123456789], - "sig": ["0x..."] - } - ``` - - - - - ```json - { - "req": [1, "auth_request", { - "address": "0x1234567890abcdef...", - "session_key": "0x9876543210fedcba...", - "application": "clearnode", - "allowances": [], - "scope": "app.create", - "expires_at": 1750659456789 - }, 1619123456789], - "sig": ["0x..."] - } - ``` - - - - -#### Migrate Channel Creation - -Implement the two-step process (create empty, then resize to fund) - -The `create_channel` method no longer accepts `amount` and `session_key` parameters: - -```json -{ - "req": [1, "create_channel", { - "chain_id": 137, - "token": "0xeeee567890abcdef...", - // remove-next-line - "amount": "100000000", - // remove-next-line - "session_key": "0x1234567890abcdef..." - }, 1619123456789], - "sig": ["0x9876fedcba..."] -} -``` - -#### Manage Session Keys - -New methods for session key operations have been added. - -##### Get Session Keys - -Request: -```json -{ - "req": [1, "get_session_keys", {}, 1619123456789], - "sig": ["0x..."] -} -``` - -Response: -```json -{ - "res": [1, "get_session_keys", { - "session_keys": [{ - "id": "sk_123", - "session_key": "0x9876543210fedcba...", - "application": "My Trading App", - "allowances": [ - { "asset": "usdc", "amount": "1000.0", "used": "250.0" } - ], - "scope": "app.create", - "expires_at": 1719123456789, - "created_at": 1619123456789 - }] - }, 1619123456789], - "sig": ["0x..."] -} -``` - -##### Revoke Session Key Request - -Request: -```json -{ - "req": [1, "revoke_session_key", { - "session_key": "0x1234567890abcdef..." - }, 1619123456789], - "sig": ["0x..."] -} -``` - -Response: -```json -{ - "res": [1, "revoke_session_key", { - "session_key": "0x1234567890abcdef..." - }, 1619123456789], - "sig": ["0x..."] -} -``` - -## 0.3.x Breaking changes - -The 0.3.x release includes breaking changes to the SDK architecture, smart contract interfaces, and Clearnode API enhancements listed below. - -**Not ready to migrate?** Unfortunately, at this time Yellow Network does not provide ClearNodes running the previous version of the protocol, so you will need to migrate to the latest version to continue using the Network. - -### Nitrolite SDK - -You should definitely read this section if you are using the Nitrolite SDK. - -#### Client: Replaced `stateWalletClient` with `StateSigner` - -The `stateWalletClient` parameter of `NitroliteClient` has been replaced with a required `stateSigner` parameter that implements the `StateSigner` interface. - -When initializing the client, you should use either `WalletStateSigner` or `SessionKeyStateSigner` to handle state signing. - -```typescript -// remove-next-line -import { createNitroliteClient } from '@erc7824/nitrolite'; -// add-start -import { - createNitroliteClient, - WalletStateSigner -} from '@erc7824/nitrolite'; -// add-end - -const client = createNitroliteClient({ - publicClient, - walletClient, - // remove-next-line - stateWalletClient: sessionWalletClient, - // add-next-line - stateSigner: new WalletStateSigner(walletClient), - addresses, -}); -``` - -**For session key signing:** - -```typescript -import { SessionKeyStateSigner } from '@erc7824/nitrolite'; - -const stateSigner = new SessionKeyStateSigner('0x...' as Hex); -``` - -#### Actions: Modified `createChannel` Parameters - -The `CreateChannelParams` interface has been fully restructured for better clarity. - -You should use the new [`CreateChannel` ClearNode API endpoint](#added-create_channel-method) to get the response, that fully resembles the channel creation parameters. - -```typescript -// remove-start -const { channelId, initialState, txHash } = await client.createChannel( - tokenAddress, - { - initialAllocationAmounts: [amount1, amount2], - stateData: '0x...', - } -); -// remove-end -// add-start -const { channelId, initialState, txHash } = await client.createChannel({ - channel: { - participants: [address1, address2], - adjudicator: adjudicatorAddress, - challenge: 86400n, - nonce: 42n, - }, - unsignedInitialState: { - intent: StateIntent.Initialize, - version: 0n, - data: '0x', - allocations: [ - { destination: address1, token: tokenAddress, amount: amount1 }, - { destination: address2, token: tokenAddress, amount: amount2 }, - ], - }, - serverSignature: '0x...', -}); -// add-end -``` - -#### Actions: Structured Typed RPC Request Parameters - -RPC requests now use endpoint-specific object-based parameters instead of untyped arrays for improved type safety. - -You should update your RPC request creation code to use the new structured format and RPC types. - -```typescript -// remove-start -const request = NitroliteRPC.createRequest( - requestId, - RPCMethod.GetChannels, - [participant, status], - timestamp -); -// remove-end -// add-start -const request = NitroliteRPC.createRequest({ - method: RPCMethod.GetChannels, - params: { - participant, - status, - }, - requestId, - timestamp, -}); -// add-end -``` - -#### Actions: Standardized Channel Operations Responses - -The responses for `CloseChannel` and `ResizeChannel` methods have been aligned with newly added `CreateChannel` endpoint for consistency. - -Update your response handling code to use the new `RPCChannelOperation` type. - -```typescript -// remove-start -export interface ResizeChannelResponseParams { - channelId: Hex; - stateData: Hex; - intent: number; - version: number; - allocations: RPCAllocation[]; - stateHash: Hex; - serverSignature: ServerSignature; -} - -export interface CloseChannelResponseParams { - channelId: Hex; - intent: number; - version: number; - stateData: Hex; - allocations: RPCAllocation[]; - stateHash: Hex; - serverSignature: ServerSignature; -} -// remove-end -// add-start -export interface RPCChannelOperation { - channelId: Hex; - state: RPCChannelOperationState; - serverSignature: Hex; -} - -export interface CreateChannelResponse extends GenericRPCMessage { - method: RPCMethod.CreateChannel; - params: RPCChannelOperation & { - channel: RPCChannel; - }; -} - -export interface ResizeChannelResponse extends GenericRPCMessage { - method: RPCMethod.ResizeChannel; - params: RPCChannelOperation; -} - -export interface CloseChannelResponse extends GenericRPCMessage { - method: RPCMethod.CloseChannel; - params: RPCChannelOperation; -} -// add-end -``` - -#### Actions: Modified `Signature` Type - -The `Signature` struct has been replaced with a simple `Hex` type to support EIP-1271 and EIP-6492 signatures. - -Update your signature-handling code to use the new `Hex` type. Still, if using Nitrolite utils correctly, you will not need to change anything, as the utils will handle the conversion for you. - -```typescript -// remove-start -interface Signature { - v: number; - r: Hex; - s: Hex; -} - -const sig: Signature = { - v: 27, - r: '0x...', - s: '0x...' -}; -// remove-end -// add-start -type Signature = Hex; - -const sig: Signature = '0x...'; -// add-end -``` - -#### Added: Pagination Types and Parameters - -To support pagination in ClearNode API requests, new types and parameters have been added. - -For now, only `GetLedgerTransactions` request has been updated to include pagination. - -```typescript -export interface PaginationFilters { - /** Pagination offset. */ - offset?: number; - /** Number of transactions to return. */ - limit?: number; - /** Sort order by created_at. */ - sort?: 'asc' | 'desc'; -} -``` - -### Clearnode API - -You should read this section only if you are using the ClearNode API directly, or if you are using the Nitrolite SDK with custom ClearNode API requests. - -#### Actions: Structured Request Parameters - -ClearNode API requests have migrated from array-based parameters to structured object parameters for improved type safety and API clarity. - -Update all your ClearNode API requests to use object-based parameters instead of arrays. - -```json -{ - // remove-next-line - "req": [1, "auth_request", [{ - // add-next-line - "req": [1, "auth_request", { - "address": "0x1234567890abcdef...", - "session_key": "0x9876543210fedcba...", - "app_name": "Example App", - // remove-next-line - "allowances": [ "usdc", "100.0" ], - // add-start - "allowances": [ - { - "asset": "usdc", - "amount": "100.0" - } - ], - // add-end - "scope": "app.create", - "expire": "3600", - "application": "0xApp1234567890abcdef..." - // remove-next-line - }], 1619123456789], - // add-next-line - }, 1619123456789], - "sig": ["0x5432abcdef..."] -} -``` - -#### Added: `create_channel` Method - -A new `create_channel` method has been added to facilitate the improved single-transaction channel opening flow. - -Use this method to request channel creation parameters from the broker, then submit the returned data to the smart contract via Nitrolite SDK or directly. - -**Request:** -```json -{ - "req": [1, "create_channel", { - "chain_id": 137, - "token": "0xeeee567890abcdef...", - "amount": "100000000", - "session_key": "0x1234567890abcdef..." // Optional - }, 1619123456789], - "sig": ["0x9876fedcba..."] -} -``` - -**Response:** -```json -{ - "res": [1, "create_channel", { - "channel_id": "0x4567890123abcdef...", - "channel": { - "participants": ["0x1234567890abcdef...", "0xbbbb567890abcdef..."], - "adjudicator": "0xAdjudicatorContractAddress...", - "challenge": 3600, - "nonce": 1619123456789 - }, - "state": { - "intent": 1, - "version": 0, - "state_data": "0xc0ffee", - "allocations": [ - { - "destination": "0x1234567890abcdef...", - "token": "0xeeee567890abcdef...", - "amount": "100000000" - }, - { - "destination": "0xbbbb567890abcdef...", - "token": "0xeeee567890abcdef...", - "amount": "0" - } - ] - }, - "server_signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c" - }, 1619123456789], - "sig": ["0xabcd1234..."] -} -``` - -#### API: Standardized Channel Operation Responses - -The responses for `create_channel`, `close_channel`, and `resize_channel` methods have been unified for consistency. - -Update your response parsing to handle the new unified structure with `channel_id`, `state`, and `server_signature` fields. - -```json -// remove-start -{ - "res": [1, "close_channel", { - "channelId": "0x4567890123abcdef...", - "intent": 3, - "version": 123, - "stateData": "0x0000000000000000000000000000000000000000000000000000000000001ec7", - "allocations": [...], - "stateHash": "0x...", - "serverSignature": "0x..." - }, 1619123456789], - "sig": ["0xabcd1234..."] -} -// remove-end -// add-start -{ - "res": [1, "close_channel", { - "channel_id": "0x4567890123abcdef...", - "state": { - "intent": 3, - "version": 123, - "state_data": "0xc0ffee", - "allocations": [ - { - "destination": "0x1234567890abcdef...", - "token": "0xeeee567890abcdef...", - "amount": "50000" - } - ] - }, - "server_signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c" - }, 1619123456789], - "sig": ["0xabcd1234..."] -} -// add-end -``` - -#### Added: Pagination Metadata - -Pagination-supporting endpoints now include a `metadata` struct in their responses with pagination information. - -Update your response handling for `get_channels`, `get_app_sessions`, `get_ledger_entries`, and `get_ledger_transactions` to use the new metadata structure. - -```json -// remove-start -{ - "res": [1, "get_channels", [ - [ - { - "channel_id": "0xfedcba9876543210...", - "status": "open", - // ... channel data - } - ] - ], 1619123456789], - "sig": ["0xabcd1234..."] -} -// remove-end -// add-start -{ - "res": [1, "get_channels", { - "channels": [ - { - "channel_id": "0xfedcba9876543210...", - "status": "open", - // ... channel data - } - ], - "metadata": { - "page": 1, - "per_page": 10, - "total_count": 56, - "page_count": 6 - } - }, 1619123456789], - "sig": ["0xabcd1234..."] -} -// add-end -``` - -The metadata fields provide: -- `page`: Current page number -- `per_page`: Number of items per page -- `total_count`: Total number of items available -- `page_count`: Total number of pages - -### Contracts - -You should read this section only if you are using the Nitrolite smart contracts directly. - -#### Action: Replaced `Signature` Struct with `bytes` - -The `Signature` struct has been removed and replaced with `bytes` type to support EIP-1271, EIP-6492, and other signature formats. - -Update all contract interactions that use signatures to pass `bytes` instead of the struct. - -```solidity -// remove-start -struct Signature { - uint8 v; - bytes32 r; - bytes32 s; -} - -function join( - bytes32 channelId, - uint256 index, - Signature calldata sig -) external returns (bytes32); - -function challenge( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs, - Signature calldata challengerSig -) external; -// remove-end -// add-start -// Signature struct is removed - -function join( - bytes32 channelId, - uint256 index, - bytes calldata sig -) external returns (bytes32); - -function challenge( - bytes32 channelId, - State calldata candidate, - State[] calldata proofs, - bytes calldata challengerSig -) external; -// add-end -``` - -#### Actions: Updated `State` Signature Array - -The `State` struct now uses `bytes[]` for signatures instead of `Signature[]`. - -```solidity -struct State { - uint8 intent; - uint256 version; - bytes data; - Allocation[] allocations; - // remove-next-line - Signature[] sigs; - // add-next-line - bytes[] sigs; -} -``` - -#### Added: Auto-Join Channel Creation Flow - -Channels can now become operational immediately after the `create()` call if all participant signatures are provided. - -When calling `create()` with complete signatures from all participants, the channel automatically becomes active without requiring a separate `join()` call. - -**Single signature (requires join):** -```solidity -// Create channel with only creator's signature -State memory initialState = State({ - intent: StateIntent.Fund, - version: 0, - data: "0x", - allocations: allocations, - sigs: [creatorSignature] // Only one signature -}); - -bytes32 channelId = custody.create(channel, initialState); -// Channel status: JOINING - requires server to call join() -``` - -**Complete signatures (auto-active):** -```solidity -// Create channel with all participants' signatures -State memory initialState = State({ - intent: StateIntent.Fund, - version: 0, - data: "0x", - allocations: allocations, - sigs: [creatorSignature, serverSignature] // All signatures -}); - -bytes32 channelId = custody.create(channel, initialState); -// Channel status: ACTIVE - ready for use immediately -``` - -#### Actions: Update Adjudicator Contracts for EIP-712 Support - -A new `EIP712AdjudicatorBase` base contract has been added to support EIP-712 typed structured data signatures in adjudicator implementations. - -The `EIP712AdjudicatorBase` provides: -- **Domain separator retrieval**: Gets EIP-712 domain separator from the channel implementation contract -- **ERC-5267 compliance**: Automatically handles EIP-712 domain data retrieval -- **Ownership management**: Built-in access control for updating channel implementation address -- **Graceful fallbacks**: Returns `NO_EIP712_SUPPORT` constant when EIP-712 is not available - -If you have custom adjudicator contracts, inherit from `EIP712AdjudicatorBase` to enable EIP-712 signature verification. - -```solidity -// remove-start -import {IAdjudicator} from "../interfaces/IAdjudicator.sol"; -import {Channel, State, Allocation, StateIntent} from "../interfaces/Types.sol"; - -contract MyAdjudicator is IAdjudicator { - function adjudicate( - Channel calldata chan, - State calldata candidate, - State[] calldata proofs - ) external view override returns (bool valid) { - return candidate.validateUnanimousSignatures(chan); - } -} -// remove-end -// add-start -import {IAdjudicator} from "../interfaces/IAdjudicator.sol"; -import {Channel, State, Allocation, StateIntent} from "../interfaces/Types.sol"; -import {EIP712AdjudicatorBase} from "./EIP712AdjudicatorBase.sol"; - -contract MyAdjudicator is IAdjudicator, EIP712AdjudicatorBase { - constructor(address owner, address channelImpl) - EIP712AdjudicatorBase(owner, channelImpl) {} - - function adjudicate( - Channel calldata chan, - State calldata candidate, - State[] calldata proofs - ) external override returns (bool valid) { - bytes32 domainSeparator = getChannelImplDomainSeparator(); - return candidate.validateUnanimousStateSignatures(chan, domainSeparator); - } -} -// add-end -``` - -#### Added: Enhanced Signature Support - -Smart contracts now support EIP-191, EIP-712, EIP-1271, and EIP-6492 signature formats for greater compatibility. - -The contracts automatically detect and verify the appropriate signature format: -- **Raw ECDSA**: Traditional `(r, s, v)` signatures -- **EIP-191**: Personal message signatures (`\x19Ethereum Signed Message:\n`) -- **EIP-712**: Typed structured data signatures -- **EIP-1271**: Smart contract wallet signatures -- **EIP-6492**: Signatures for undeployed contracts - -No changes are needed in your contract calls - the signature verification is handled automatically by the contract. diff --git a/erc7824-docs/docs/index.md b/erc7824-docs/docs/index.md deleted file mode 100644 index 09c3c9d3e..000000000 --- a/erc7824-docs/docs/index.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -sidebar_position: 1 -title: Introduction -description: Build scalable web3 applications with state channels using Nitrolite. -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, javascript, typescript, sdk] ---- - -import { Card, CardGrid } from '@site/src/components/Card'; - -# Introduction - -Welcome to Nitrolite! Built on the ERC-7824 standard, Nitrolite is a powerful framework that enables developers to build high-performance decentralized applications with near-instant finality. - -The following guides will walk you through the complete lifecycle of Nitrolite applications, from client initialization to application sessions. Whether you're building payment systems, games, financial applications, or any use case requiring high-frequency transactions, Nitrolite provides the infrastructure you need. - - - - - - - - - - -## Key Features - -- **Instant Transactions**: Off-chain operations mean no waiting for block confirmations. -- **Minimal Gas Fees**: On-chain gas is primarily for channel opening and settlement. -- **High Throughput**: Capable of handling thousands of transactions per second. -- **Application Flexibility**: Ideal for games, payment systems, real-time interactions, and more. - -## Core SDK Architecture - -The Nitrolite SDK is designed with modularity and broad compatibility in mind: - -1. **NitroliteClient**: This is the main entry point for developers. It provides a high-level API to manage the lifecycle of state channels, including deposits, channel creation, application session management, and withdrawals. - -2. **Nitrolite RPC**: This component handles the secure, real-time, off-chain communication between channel participants and the broker. It's responsible for message signing, verification, and routing. diff --git a/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md b/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md deleted file mode 100644 index ce7b5e4c5..000000000 --- a/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md +++ /dev/null @@ -1,427 +0,0 @@ ---- -sidebar_position: 1 -title: Abstract Accounts -description: Using NitroliteClient with Account Abstraction -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, account abstraction, ERC-4337] ---- - -import MethodDetails from '@site/src/components/MethodDetails'; -import { Card, CardGrid } from '@site/src/components/Card'; - -# Using with Abstract Accounts - -The `NitroliteClient` provides special support for [ERC-4337 Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337) through the `txPreparer` object. This allows dApps using smart contract wallets to prepare transactions without executing them, enabling batching and other advanced patterns. - -## Transaction Preparer Overview - -The `txPreparer` is a property of the `NitroliteClient` that provides methods for preparing transaction data without sending it to the blockchain. Each method returns one or more [`PreparedTransaction`](../types.md#preparedtransaction) objects that can be used with Account Abstraction providers. - -```typescript -import { NitroliteClient } from '@erc7824/nitrolite'; - -const client = new NitroliteClient({/* config */}); - -// Instead of: await client.deposit(amount) -const txs = await client.txPreparer.prepareDepositTransactions(amount); -``` - -## Transaction Preparation Methods - -These methods allow you to prepare transactions for the entire channel lifecycle without executing them. - -### 1. Deposit Methods - -`} - example={`// Prepare deposit transaction(s) - may include ERC20 approval -const txs = await client.txPreparer.prepareDepositTransactions(1000000n); - -// For each prepared transaction -for (const tx of txs) { - await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data, - value: tx.value || 0n - }); -}`} -/> - -`} - example={`// Prepare approval transaction -const tx = await client.txPreparer.prepareApproveTokensTransaction(2000000n); - -// Send through your AA provider -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - -### 2. Channel Creation Methods - -`} - example={`// Prepare channel creation transaction -const tx = await client.txPreparer.prepareCreateChannelTransaction({ - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' -}); - -// Send it through your Account Abstraction provider -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data, - value: tx.value || 0n -});`} -/> - -`} - example={`// Prepare deposit + channel creation (potentially 3 txs: approve, deposit, create) -const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions( - 1000000n, - { - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' - } -); - -// Bundle these transactions into a single UserOperation -await aaProvider.sendUserOperation({ - userOperations: txs.map(tx => ({ - target: tx.to, - data: tx.data, - value: tx.value || 0n - })) -});`} -/> - -### 3. Channel Operation Methods - -`} - example={`// Prepare checkpoint transaction -const tx = await client.txPreparer.prepareCheckpointChannelTransaction({ - channelId: '0x...', - candidateState: state -}); - -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - -`} - example={`// Prepare challenge transaction -const tx = await client.txPreparer.prepareChallengeChannelTransaction({ - channelId: '0x...', - candidateState: state -}); - -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - -`} - example={`// Prepare resize transaction -const tx = await client.txPreparer.prepareResizeChannelTransaction({ - channelId: '0x...', - candidateState: state -}); - -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - -### 4. Channel Closing Methods - -`} - example={`// Prepare close channel transaction -const tx = await client.txPreparer.prepareCloseChannelTransaction({ - finalState: { - channelId: '0x...', - stateData: '0x...', - allocations: [allocation1, allocation2], - version: 10n, - serverSignature: signature - } -}); - -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - -### 5. Withdrawal Methods - -`} - example={`// Prepare withdrawal transaction -const tx = await client.txPreparer.prepareWithdrawalTransaction(500000n); - -await aaProvider.sendUserOperation({ - target: tx.to, - data: tx.data -});`} -/> - - -## Understanding PreparedTransaction - -The [`PreparedTransaction`](../types.md#preparedtransaction) type is the core data structure returned by all transaction preparation methods. It contains all the information needed to construct a transaction or UserOperation: - -```typescript -type PreparedTransaction = { - // Target contract address - to: Address; - - // Contract call data - data?: Hex; - - // ETH value to send (0n for token operations) - value?: bigint; -}; -``` - -Each `PreparedTransaction` represents a single contract call that can be: - -1. **Executed directly** - If you're using a standard EOA wallet -2. **Bundled into a UserOperation** - For account abstraction providers -3. **Batched with other transactions** - For advanced use cases - -## Integration Examples - -The Nitrolite transaction preparer can be integrated with any Account Abstraction provider. Here are examples with popular AA SDKs: - -### With Safe Account Abstraction SDK - -```typescript -import { NitroliteClient } from '@erc7824/nitrolite'; -import { AccountAbstraction } from '@safe-global/account-abstraction-kit-poc'; - -// Initialize clients -const client = new NitroliteClient({/* config */}); -const aaKit = new AccountAbstraction(safeProvider); - -// Prepare transaction -const tx = await client.txPreparer.prepareCreateChannelTransaction({ - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' -}); - -// Send through AA provider -const safeTransaction = await aaKit.createTransaction({ - transactions: [{ - to: tx.to, - data: tx.data, - value: tx.value?.toString() || '0' - }] -}); - -const txResponse = await aaKit.executeTransaction(safeTransaction); -``` - -### With Biconomy SDK - -```typescript -import { NitroliteClient } from '@erc7824/nitrolite'; -import { BiconomySmartAccountV2 } from "@biconomy/account"; - -// Initialize clients -const client = new NitroliteClient({/* config */}); -const smartAccount = await BiconomySmartAccountV2.create({/* config */}); - -// Prepare transaction -const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions( - 1000000n, - { - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' - } -); - -// Build user operation -const userOp = await smartAccount.buildUserOp( - txs.map(tx => ({ - to: tx.to, - data: tx.data, - value: tx.value || 0n - })) -); - -// Send user operation -const userOpResponse = await smartAccount.sendUserOp(userOp); -await userOpResponse.wait(); -``` - -## Advanced Use Cases - -The transaction preparer is especially powerful when combined with advanced Account Abstraction features. - -### Batching Multiple Operations - -One of the main advantages of Account Abstraction is the ability to batch multiple operations into a single transaction: - -```typescript -// Collect prepared transactions from different operations -const preparedTxs = []; - -// 1. Add token approval if needed -const allowance = await client.getTokenAllowance(); -if (allowance < totalNeeded) { - const approveTx = await client.txPreparer.prepareApproveTokensTransaction(totalNeeded); - preparedTxs.push(approveTx); -} - -// 2. Add deposit -const depositTx = await client.txPreparer.prepareDepositTransactions(amount); -preparedTxs.push(...depositTx); - -// 3. Add channel creation -const createChannelTx = await client.txPreparer.prepareCreateChannelTransaction(params); -preparedTxs.push(createChannelTx); - -// 4. Execute all as a batch with your AA provider -await aaProvider.sendUserOperation({ - userOperations: preparedTxs.map(tx => ({ - target: tx.to, - data: tx.data, - value: tx.value || 0n - })) -}); -``` - -### Gas Sponsoring - -Account Abstraction enables gas sponsorship, where someone else pays for the transaction gas: - -```typescript -// Prepare transaction -const tx = await client.txPreparer.prepareCreateChannelTransaction(params); - -// Use a sponsored transaction -await paymasterProvider.sponsorTransaction({ - target: tx.to, - data: tx.data, - value: tx.value || 0n, - user: userAddress -}); -``` - -### Session Keys - -Some AA wallets support session keys, which are temporary keys with limited permissions: - -```typescript -// Create a session key with permissions only for specific operations -const sessionKeyData = await aaWallet.createSessionKey({ - permissions: [ - { - target: client.addresses.custody, - // Only allow specific functions - functionSelector: [ - "0xdeposit(...)", - "0xwithdraw(...)" - ] - } - ], - expirationTime: Date.now() + 3600 * 1000 // 1 hour -}); - -// Use the session key to prepare and send transactions -const tx = await client.txPreparer.prepareDepositTransactions(amount); -await aaWallet.executeWithSessionKey(sessionKeyData, { - target: tx.to, - data: tx.data, - value: tx.value || 0n -}); -``` - -## Best Practices - -
-
-

Batch Related Operations

-

Use prepareDepositAndCreateChannelTransactions to batch deposit and channel creation into a single user operation.

-
- -
-

Handle Approvals

-

For ERC20 tokens, prepareDepositTransactions will include an approval transaction if needed. Always process all returned transactions.

-
- -
-

State Signing

-

Even when using Account Abstraction, state signatures are handled separately using the stateWalletClient (or walletClient if not specified).

-
- -
-

Error Handling

-

The preparation methods throw the same errors as their execution counterparts, so use the same error handling patterns.

-
- -
-

Check Token Allowances

-

Before preparing token operations, you can check if approval is needed:

-
-      
-const allowance = await client.getTokenAllowance();
-if (allowance < amount) {
-  // Need approval
-}
-      
-    
-
- -
-

Gas Estimation

-

When using Account Abstraction, gas estimation is typically handled by the AA provider, but you can request estimates if needed.

-
-
- -## Limitations - -:::caution Important -- The transaction preparer **doesn't handle sequencing or nonce management** - that's the responsibility of your AA provider. -- Some operations (like checkpointing) require signatures from all participants, which must be collected separately from the transaction preparation. -::: \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md b/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md deleted file mode 100644 index c8e3c6e23..000000000 --- a/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md +++ /dev/null @@ -1,218 +0,0 @@ ---- -sidebar_position: 3 -title: Erc20Service -description: Documentation for the ERC20Service class -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced, erc20, tokens] ---- - -# Erc20Service - -The `Erc20Service` class provides a convenient interface for interacting with ERC20 tokens. It handles token approvals, allowance checks, and balance inquiries that are essential for deposit and withdrawal operations in the Nitrolite system. - -## Initialization - -```typescript -import { Erc20Service } from '@erc7824/nitrolite'; - -const erc20Service = new Erc20Service( - publicClient, // viem PublicClient - walletClient // viem WalletClient -); -``` - -## Core Methods - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `approve` | Approves a spender to use tokens. | `tokenAddress: Address, spender: Address, amount: bigint` | `Promise` | -| `getTokenAllowance` | Gets token allowance for a spender. | `tokenAddress: Address, owner: Address, spender: Address` | `Promise` | -| `getTokenBalance` | Gets token balance for an account. | `tokenAddress: Address, account: Address` | `Promise` | - -## Method Details - -### Approve Tokens - -Approves a spender (typically the Custody contract) to transfer tokens on behalf of the owner. - -```typescript -// Approve the custody contract to spend 1000 tokens -const txHash = await erc20Service.approve( - tokenAddress, // ERC20 token address - spenderAddress, // Custody contract address - 1000000000000000000n // Amount to approve (1 token with 18 decimals) -); -``` - -**Important notes:** -- For security reasons, always specify the exact amount you want to approve -- Consider using the ERC20 token decimals for the amount calculation -- The transaction will fail if the owner has insufficient balance - -### Get Token Allowance - -Retrieves the current allowance granted by an owner to a spender. - -```typescript -// Check current allowance -const allowance = await erc20Service.getTokenAllowance( - tokenAddress, // ERC20 token address - ownerAddress, // Owner's address - spenderAddress // Spender's address (custody contract) -); - -console.log(`Current allowance: ${allowance}`); - -// Check if allowance is sufficient -if (allowance < requiredAmount) { - console.log('Need to approve more tokens'); -} -``` - -### Get Token Balance - -Retrieves the token balance for a specific account. - -```typescript -// Check token balance -const balance = await erc20Service.getTokenBalance( - tokenAddress, // ERC20 token address - accountAddress // Account to check -); - -console.log(`Account balance: ${balance}`); - -// Check if balance is sufficient -if (balance < requiredAmount) { - console.log('Insufficient token balance'); -} -``` - -## Transaction Preparation - -For Account Abstraction support, `Erc20Service` provides a transaction preparation method: - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `prepareApprove` | Prepares an approval transaction. | `tokenAddress: Address, spender: Address, amount: bigint` | `Promise` | - -Example: -```typescript -// Prepare approval transaction -const tx = await erc20Service.prepareApprove( - tokenAddress, - spenderAddress, - amount -); - -// Use with your Account Abstraction provider -const userOp = await aaProvider.buildUserOperation({ - target: tx.to, - data: tx.data, - value: 0n // ERC20 approvals don't require ETH -}); -``` - -## Implementation Details - -The `Erc20Service` uses the standard ERC20 interface methods: - -- `approve`: Allows a spender to withdraw tokens from the owner's account, up to the specified amount -- `allowance`: Returns the remaining tokens that a spender is allowed to withdraw -- `balanceOf`: Returns the token balance of the specified account - -## Working with Token Decimals - -ERC20 tokens typically have decimal places (most commonly 18). When working with token amounts, you should account for these decimals: - -```typescript -import { parseUnits } from 'viem'; - -// For a token with 18 decimals -const tokenDecimals = 18; - -// Convert 1.5 tokens to the smallest unit -const amount = parseUnits('1.5', tokenDecimals); - -// Approve the amount -await erc20Service.approve(tokenAddress, spenderAddress, amount); -``` - -## Error Handling - -The `Erc20Service` throws specific error types: - -- `TokenError`: For token-specific errors (insufficient balance, approval failures) -- `ContractCallError`: When calls to the contract fail -- `WalletClientRequiredError`: When wallet client is needed but not provided - -Example: -```typescript -try { - await erc20Service.approve(tokenAddress, spenderAddress, amount); -} catch (error) { - if (error instanceof TokenError) { - console.error(`Token error: ${error.message}`); - console.error(`Suggestion: ${error.suggestion}`); - - // Check for specific token error conditions - if (error.details?.errorName === 'InsufficientBalance') { - console.log(`Available balance: ${error.details.available}`); - } - } -} -``` - -## Common Patterns - -### Checking and Approving Tokens - -A common pattern is to check if the current allowance is sufficient before approving more tokens: - -```typescript -// Get current allowance -const allowance = await erc20Service.getTokenAllowance( - tokenAddress, - ownerAddress, - spenderAddress -); - -// If allowance is insufficient, approve more tokens -if (allowance < requiredAmount) { - await erc20Service.approve(tokenAddress, spenderAddress, requiredAmount); -} - -// Now proceed with the operation that requires the approval -// (e.g., deposit into custody contract) -``` - -### Handling Multiple Tokens - -If your application works with multiple tokens, you can reuse the same `Erc20Service` instance: - -```typescript -// Same service instance for different tokens -const erc20Service = new Erc20Service(publicClient, walletClient); - -// Work with token A -const balanceA = await erc20Service.getTokenBalance(tokenAddressA, accountAddress); - -// Work with token B -const balanceB = await erc20Service.getTokenBalance(tokenAddressB, accountAddress); -``` - -## Integration with NitroliteClient - -When using the `NitroliteClient`, you typically don't need to interact with `Erc20Service` directly, as the client handles these operations for you: - -```typescript -// NitroliteClient handles token approvals automatically during deposit -await nitroliteClient.deposit(amount); - -// For explicit approval without deposit -await nitroliteClient.approveTokens(amount); - -// Get token balance through the client -const balance = await nitroliteClient.getTokenBalance(); -``` - -However, for advanced use cases or custom token interaction, direct access to `Erc20Service` can be useful. \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/advanced/index.md b/erc7824-docs/docs/nitrolite_client/advanced/index.md deleted file mode 100644 index 01f03d640..000000000 --- a/erc7824-docs/docs/nitrolite_client/advanced/index.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -sidebar_position: 3 -title: Advanced Usage -description: Advanced topics for working with the NitroliteClient -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced] ---- - -# Advanced Usage - -This section covers advanced topics for working with the `@erc7824/nitrolite` SDK. These are intended for developers who need deeper control over the state channel operations or want to integrate with specialized systems. - -## Available Topics - -### Low-level Services - -The NitroliteClient is built on top of specialized services that can be used directly for more fine-grained control: - -- [NitroliteService](./nitrolite-service.md) - Core service for interacting with the custody contract and managing channels -- [Erc20Service](./erc20-service.md) - Service for interacting with ERC20 tokens - -### Account Abstraction Integration - -- [Abstract Accounts](./abstract-accounts.md) - Using NitroliteClient with ERC-4337 Account Abstraction for smart contract wallets - -## When to Use Advanced Features - -Consider using the advanced features when: - -1. **Building custom workflows** that require more control than the high-level NitroliteClient API provides -2. **Integrating with smart contract wallets** or other account abstraction systems -3. **Implementing specialized monitoring or management systems** for state channels -4. **Developing cross-chain applications** that require custom handling of state channel operations -5. **Optimizing gas usage** through transaction batching and other techniques - -## Example: Direct Service Usage - -While using the high-level NitroliteClient is recommended for most applications, here's how you can work directly with the services: - -```typescript -import { - NitroliteService, - Erc20Service, - getChannelId, - signState -} from '@erc7824/nitrolite'; - -// Initialize services -const nitroliteService = new NitroliteService( - publicClient, - addresses, - walletClient, - account.address -); - -const erc20Service = new Erc20Service( - publicClient, - walletClient -); - -// Check token allowance -const allowance = await erc20Service.getTokenAllowance( - tokenAddress, - account.address, - addresses.custody -); - -// Approve tokens if needed -if (allowance < depositAmount) { - await erc20Service.approve(tokenAddress, addresses.custody, depositAmount); -} - -// Deposit funds -await nitroliteService.deposit(tokenAddress, depositAmount); - -// Create channel with custom parameters -const channelNonce = generateChannelNonce(account.address); -const channel = { - participants: [account.address, counterpartyAddress], - adjudicator: addresses.adjudicator, - challenge: 100n, // Challenge duration in seconds - nonce: channelNonce -}; - -// Prepare initial state -const initialState = { - intent: StateIntent.INITIALIZE, - version: 0n, - data: '0x1234', // Application-specific data - allocations: [ - { destination: account.address, token: tokenAddress, amount: 700000n }, - { destination: counterpartyAddress, token: tokenAddress, amount: 300000n } - ], - sigs: [] // Will be filled with signatures -}; - -// Sign the state -const channelId = getChannelId(channel); -const stateHash = getStateHash(channelId, initialState); -const signature = await signState(walletClient, stateHash); -initialState.sigs = [signature]; - -// Create the channel -await nitroliteService.createChannel(channel, initialState); -``` - -## Example: Complex Transaction Preparation - -For applications using Account Abstraction, you can prepare complex transaction sequences: - -```typescript -import { NitroliteClient, NitroliteTransactionPreparer } from '@erc7824/nitrolite'; - -// Initialize client -const client = new NitroliteClient({/* config */}); - -// Access the transaction preparer directly -const txPreparer = client.txPreparer; - -// Prepare a complete sequence (approve, deposit, create channel) -const allTxs = []; - -// 1. Check and prepare token approval if needed -const allowance = await client.getTokenAllowance(); -if (allowance < depositAmount) { - const approveTx = await txPreparer.prepareApproveTokensTransaction(depositAmount); - allTxs.push(approveTx); -} - -// 2. Prepare deposit -const depositTx = await txPreparer.prepareDepositTransactions(depositAmount); -allTxs.push(...depositTx); - -// 3. Prepare channel creation -const createChannelTx = await txPreparer.prepareCreateChannelTransaction({ - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' -}); -allTxs.push(createChannelTx); - -// 4. Use with your Account Abstraction provider -await aaProvider.sendUserOperation({ - userOperations: allTxs.map(tx => ({ - target: tx.to, - data: tx.data, - value: tx.value || 0n - })) -}); -``` - -These advanced techniques give you greater flexibility and control over the state channel operations, but they also require a deeper understanding of the underlying protocol. \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md b/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md deleted file mode 100644 index c4452fc41..000000000 --- a/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -sidebar_position: 2 -title: NitroliteService -description: Documentation for the core NitroliteService class -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced] ---- - -# NitroliteService - -The `NitroliteService` class is the core service that directly interacts with the Nitrolite Custody smart contract. It handles channel management, deposits, withdrawals, and all other channel-specific operations following the channel lifecycle. - -## Initialization - -```typescript -import { NitroliteService } from '@erc7824/nitrolite'; - -const nitroliteService = new NitroliteService( - publicClient, // viem PublicClient - addresses, // ContractAddresses - walletClient, // viem WalletClient - account // Account address -); -``` - -## Channel Lifecycle Methods - -### 1. Deposit Operations - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `deposit` | Deposits tokens/ETH into the custody contract. | `tokenAddress: Address, amount: bigint` | `Promise` | -| `withdraw` | Withdraws tokens from the custody contract. | `tokenAddress: Address, amount: bigint` | `Promise` | - -Example: -```typescript -// Deposit ETH or token -const txHash = await nitroliteService.deposit(tokenAddress, amount); - -// Withdraw ETH or token -const txHash = await nitroliteService.withdraw(tokenAddress, amount); -``` - -### 2. Channel Creation - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `createChannel` | Creates a new channel with the given parameters. | `channel: Channel, initialState: State` | `Promise` | - -Example: -```typescript -// Create a channel -const txHash = await nitroliteService.createChannel(channel, initialState); -``` - -Where: -- `channel` defines the participants, adjudicator, challenge period, and nonce -- `initialState` contains the initial allocation of funds and state data - -### 3. Channel Operations - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `checkpoint` | Checkpoints a state on-chain. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | -| `challenge` | Challenges a channel with a candidate state. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | -| `resize` | Resizes a channel with a candidate state. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | - -Example: -```typescript -// Checkpoint a channel state -const txHash = await nitroliteService.checkpoint(channelId, candidateState); - -// Challenge a channel -const txHash = await nitroliteService.challenge(channelId, candidateState); - -// Resize a channel -const txHash = await nitroliteService.resize(channelId, candidateState); -``` - -### 4. Channel Closing - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `close` | Closes a channel using a final state. | `channelId: ChannelId, finalState: State` | `Promise` | - -Example: -```typescript -// Close a channel -const txHash = await nitroliteService.close(channelId, finalState); -``` - -### 5. Account Information - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `getAccountChannels` | Gets channel IDs for an account. | `accountAddress: Address` | `Promise` | -| `getAccountInfo` | Gets account info for a token. | `accountAddress: Address, tokenAddress: Address` | `Promise` | - -Example: -```typescript -// Get all channels for an account -const channels = await nitroliteService.getAccountChannels(accountAddress); - -// Get detailed account info -const info = await nitroliteService.getAccountInfo(accountAddress, tokenAddress); -console.log(`Available: ${info.available}, Locked: ${info.locked}`); -``` - -## Transaction Preparation Methods - -For Account Abstraction support, NitroliteService provides transaction preparation methods that return transaction data without executing it: - -| Method | Description | Parameters | Return Type | -|--------|-------------|------------|------------| -| `prepareDeposit` | Prepares deposit transaction. | `tokenAddress: Address, amount: bigint` | `Promise` | -| `prepareCreateChannel` | Prepares channel creation transaction. | `channel: Channel, initialState: State` | `Promise` | -| `prepareCheckpoint` | Prepares checkpoint transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | -| `prepareChallenge` | Prepares challenge transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | -| `prepareResize` | Prepares resize transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` | -| `prepareClose` | Prepares close transaction. | `channelId: ChannelId, finalState: State` | `Promise` | -| `prepareWithdraw` | Prepares withdraw transaction. | `tokenAddress: Address, amount: bigint` | `Promise` | - -Example: -```typescript -// Prepare deposit transaction -const tx = await nitroliteService.prepareDeposit(tokenAddress, amount); - -// Use with your Account Abstraction provider -const userOp = await aaProvider.buildUserOperation({ - target: tx.to, - data: tx.data, - value: tx.value || 0n -}); -``` - -## Implementation Details - -The `NitroliteService` connects to the Custody contract using: - -- A viem `PublicClient` for read operations -- A viem `WalletClient` for write operations and signing -- The contract address specified in the configuration - -The service handles: -- Contract interaction -- Parameter validation -- Error handling -- Transaction preparation - -## Error Handling - -The `NitroliteService` throws specific error types: - -- `ContractCallError`: When calls to the contract fail -- `InvalidParameterError`: When parameters are invalid -- `MissingParameterError`: When required parameters are missing -- `WalletClientRequiredError`: When wallet client is needed but not provided -- `AccountRequiredError`: When account is needed but not provided - -Example: -```typescript -try { - await nitroliteService.deposit(tokenAddress, amount); -} catch (error) { - if (error instanceof ContractCallError) { - console.error(`Contract call failed: ${error.message}`); - console.error(`Suggestion: ${error.suggestion}`); - } -} -``` - -## Advanced Usage - -### Custom Contract Interaction - -For advanced use cases, you might need to interact directly with the contract: - -```typescript -// Get the custody contract address -const custodyAddress = nitroliteService.custodyAddress; - -// Use with your own custom contract interaction -const customContract = getContract({ - address: custodyAddress, - abi: custodyAbi, - // Additional configuration... -}); -``` - -### Multiple Channel Management - -For applications managing multiple channels: - -```typescript -// Get all channels for the account -const channels = await nitroliteService.getAccountChannels(accountAddress); - -// Process each channel -for (const channelId of channels) { - // Get channel info from contract - // Process channel state -} -``` \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md b/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md deleted file mode 100644 index 9422f138e..000000000 --- a/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -sidebar_position: 4 -title: Supported Signature Formats -description: Documentation for supported signature formats in NitroliteClient -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced, signature, format, ECDSA, EIP-191, EIP-712, EIP-1271, EIP-6492] ---- - -# Supported Signature Formats - -The nitrolite smart contract supports multiple signature formats over a State to accommodate various use cases and compatibility with different wallets and applications. - -The message being signed is a channelId and State, formatted in a specific way. The most common is a `packedState`, which is calculated as follows: - -```solidity -abi.encode(channelId, state.intent, state.version, state.data, state.allocations) -``` - -## EOA signatures - -Externally Owned Accounts (EOAs) can sign messages with their private key using the ECDSA. - -Based on how the message is handled before signing, the following formats are supported: - -### Raw ECDSA Signature - -The message is a `packedState`, that is hashed with `keccak256` before signing. The signature is a 65-byte ECDSA signature. - -### EIP-191 Signature - -You can read more about EIP-191 in the [EIP-191 specification](https://eips.ethereum.org/EIPS/eip-191). - -The message is a `packedState` prefixed with `"\x19Ethereum Signed Message:\n" + len(packedState)` and hashed with `keccak256` before signing. The signature is a 65-byte ECDSA signature. - -### EIP-712 Signature - -You can read more about EIP-712 in the [EIP-712 specification](https://eips.ethereum.org/EIPS/eip-712). - -The message is an `AllowStateHash` typed data, calculated as follows: - -```solidity -abi.encode( - typeHash, - channelId, - state.intent, - state.version, - keccak256(state.data), - keccak256(abi.encode(state.allocations)) -); -``` - -Where `typeHash` is `AllowStateHash(bytes32 channelId,uint8 intent,uint256 version,bytes data,Allocation[] allocations)Allocation(address destination,address token,uint256 amount)`. - -The message is then hashed with `keccak256`, appended to `"\x19\x01" || domainSeparator` and signed. The signature is a 65-byte ECDSA signature. - -`||` is a concatenation operator, and `domainSeparator` is calculated as follows: - -```solidity -keccak256( - abi.encode( - EIP712_TYPE_HASH, - keccak256(name), - keccak256(version), - chainId, - verifyingContract - ) -); -``` - -`EIP712_TYPE_HASH` is `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. - -Additionally, `name`, `version` are the name and version of the Custody contract, `chainId` is the chain ID of the network, and `verifyingContract` is the address of the contract. - -## Smart Contract Signatures - -Smart Contracts that support [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) or [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) can sign messages using their own logic. When checking such signatures, the nitrolite smart contract will pass the `keccak256` hash of the `packedState` as a message hash for verification. - -See the aforementioned EIP standards for details on how these signatures are structured and verified. If you want to add support for such signatures in your client, you probably need to look at how signature verification logic is implemented in the Smart Contract (Smart Wallet, etc) that will use them. - -## Challenge Signatures - -The aforementioned signature formats are used to sign States, however to submit a challenge, the user must provide a `challengerSignature`, which proves that the user has the right to challenge a Channel. - -Depending on a signature format, the `challengerSignature` is calculated differently from the common State signature: - -- **Raw ECDSA, EIP-191, EIP-1271 and EIP-6492**: The message (`packedState`) is suffixed with a `challenge` string (`abi.encodePacked(packedState, "challenge")`). -- **EIP-712**: The `typeHash` name is `AllowChallengeStateHash`, while type format remains the same. diff --git a/erc7824-docs/docs/nitrolite_client/index.mdx b/erc7824-docs/docs/nitrolite_client/index.mdx deleted file mode 100644 index 0ddb5914f..000000000 --- a/erc7824-docs/docs/nitrolite_client/index.mdx +++ /dev/null @@ -1,122 +0,0 @@ ---- -sidebar_position: 2 -title: NitroliteClient -description: Nitrolite client SDK documentation -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial] ---- - -import { Card, CardGrid } from '@site/src/components/Card'; - -# NitroliteClient - -The `NitroliteClient` class is the main entry point for interacting with Nitrolite state channels. It provides a comprehensive set of methods for managing channels, deposits, and funds. - - - - - - - - -## Quick Start - -```typescript -import { NitroliteClient } from '@erc7824/nitrolite'; - -// Initialize client -const nitroliteClient = new NitroliteClient({ - publicClient, - walletClient, - addresses: { - custody: '0x...', - adjudicator: '0x...', - guestAddress: '0x...', - tokenAddress: '0x...' - }, - chainId: 137, // Polygon mainnet - challengeDuration: 100n -}); - -// 1. Deposit funds -const depositTxHash = await nitroliteClient.deposit(1000000n); - -// 2. Create a channel -const { channelId, initialState, txHash } = await nitroliteClient.createChannel({ - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' -}); - -// 3. Resize the channel when needed -const resizeTxHash = await nitroliteClient.resizeChannel({ - resizeState: { - channelId, - stateData: '0x5678', - allocations: newAllocations, - version: 2n, - intent: StateIntent.RESIZE, - serverSignature: signature - }, - proofStates: [] -}); - -// 4. Close the channel -const closeTxHash = await nitroliteClient.closeChannel({ - finalState: { - channelId, - stateData: '0x5678', - allocations: newAllocations, - version: 5n, - serverSignature: signature - } -}); - -// 5. Withdraw funds -const withdrawTxHash = await nitroliteClient.withdrawal(800000n); -``` - -{/* ## Learning Path - - - - - - - - - */} \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/methods.mdx b/erc7824-docs/docs/nitrolite_client/methods.mdx deleted file mode 100644 index 2cd7f3973..000000000 --- a/erc7824-docs/docs/nitrolite_client/methods.mdx +++ /dev/null @@ -1,236 +0,0 @@ ---- -sidebar_position: 1 -title: Methods -description: Complete API reference for NitroliteClient -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial] ---- - -import MethodDetails from '@site/src/components/MethodDetails'; -import { Card, CardGrid } from '@site/src/components/Card'; - -# Methods - -This page provides a complete reference for all methods available in the `NitroliteClient` class from the `@erc7824/nitrolite` package. - -## Channel Lifecycle Methods - -These methods are organized according to the typical lifecycle of a state channel. - -### 1. Deposit Methods - -The deposit phase includes methods for managing funds in the custody contract and handling token approvals. - - - - - - - - - -### 2. Channel Creation Methods - - - - - -### 3. Channel Operation Methods - - - - - - - -### 4. Channel Closing Methods - - - -### 5. Withdrawal Methods - - - -## Account Information Methods - -These methods provide information about your account's state: - - - - - -:::caution Advanced Usage: Transaction Preparation -For Account Abstraction support and transaction preparation methods, see the [Abstract Accounts](./advanced/abstract-accounts) page. -::: - -## Example: Complete Channel Lifecycle - -```typescript -import { NitroliteClient } from '@erc7824/nitrolite'; - -// Initialize the client -const client = new NitroliteClient({ - publicClient, - walletClient, - addresses: { custody, adjudicator, guestAddress, tokenAddress }, - chainId: 137, - challengeDuration: 100n -}); - -// 1. Deposit funds -const depositTxHash = await client.deposit(1000000n); - -// 2. Create a channel -const { channelId, initialState } = await client.createChannel({ - initialAllocationAmounts: [700000n, 300000n], - stateData: '0x1234' -}); - -// 3. Get account info to verify funds are locked -const accountInfo = await client.getAccountInfo(); -console.log(`Locked in channels: ${accountInfo.locked}`); - -// 4. Later, resize the channel -const resizeTxHash = await client.resizeChannel({ - resizeState: { - channelId, - stateData: '0x5678', - allocations: newAllocations, - version: 2n, - intent: StateIntent.RESIZE, - serverSignature: signature - }, - proofStates: [] -}); - -// 5. Close the channel -const closeTxHash = await client.closeChannel({ - finalState: { - channelId, - stateData: '0x5678', - allocations: [ - { destination: userAddress, token: tokenAddress, amount: 800000n }, - { destination: counterpartyAddress, token: tokenAddress, amount: 200000n } - ], - version: 5n, - serverSignature: signature - } -}); - -// 6. Withdraw funds -const withdrawTxHash = await client.withdrawal(800000n); -``` \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_client/types.md b/erc7824-docs/docs/nitrolite_client/types.md deleted file mode 100644 index e82cec173..000000000 --- a/erc7824-docs/docs/nitrolite_client/types.md +++ /dev/null @@ -1,371 +0,0 @@ ---- -sidebar_position: 2 -title: Type Definitions -description: Detailed type definitions used in the NitroliteClient -keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial] ---- - -# Type Definitions - -This page documents the core types used throughout the `@erc7824/nitrolite` SDK. Understanding these types is essential for effectively working with the `NitroliteClient`. - -## Core Types - -### ChannelId - -```typescript -type ChannelId = Hex; -``` - -A unique identifier for a state channel, represented as a hexadecimal string. - -### StateHash - -```typescript -type StateHash = Hex; -``` - -A hash of a channel state, represented as a hexadecimal string. - -### Signature - -```typescript -type Signature = Hex; -``` - -Represents a cryptographic signature used for signing state channel states as a hexadecimal string. - -### Allocation - -```typescript -interface Allocation { - destination: Address; // Where funds are sent on channel closure - token: Address; // ERC-20 token address (zero address for ETH) - amount: bigint; // Token amount allocated -} -``` - -Represents the allocation of funds to a particular destination. - -### Channel - -```typescript -interface Channel { - participants: [Address, Address]; // List of participants [Host, Guest] - adjudicator: Address; // Address of the contract that validates states - challenge: bigint; // Duration in seconds for challenge period - nonce: bigint; // Unique per channel with same participants and adjudicator -} -``` - -Represents the core configuration of a state channel. - -### StateIntent - -```typescript -enum StateIntent { - OPERATE, // Operate the state application - INITIALIZE, // Initial funding state - RESIZE, // Resize state - FINALIZE, // Final closing state -} -``` - -Indicates the intent of a state update. The intent determines how the state is processed by the channel participants and the blockchain. - -### State - -```typescript -interface State { - intent: StateIntent; // Intent of the state - version: bigint; // Version number, incremented for each update - data: Hex; // Application data encoded as hex - allocations: [Allocation, Allocation]; // Asset allocation for each participant - sigs: Signature[]; // State hash signatures -} -``` - -Represents a complete state channel state, including allocations and signatures. - -## Configuration Types - -### NitroliteClientConfig - -```typescript -interface NitroliteClientConfig { - /** The viem PublicClient for reading blockchain data. */ - publicClient: PublicClient; - - /** - * The viem WalletClient used for: - * 1. Sending on-chain transactions in direct execution methods (e.g., `client.deposit`). - * 2. Providing the 'account' context for transaction preparation (`client.txPreparer`). - * 3. Signing off-chain states *if* `stateWalletClient` is not provided. - */ - walletClient: WalletClient>; - - /** - * Optional: A separate viem WalletClient used *only* for signing off-chain state updates (`signMessage`). - * Provide this if you want to use a different key (e.g., a "hot" key from localStorage) - * for state signing than the one used for on-chain transactions. - * If omitted, `walletClient` will be used for state signing. - */ - stateWalletClient?: WalletClient>; - - /** Contract addresses required by the SDK. */ - addresses: ContractAddresses; - - /** Chain ID for the channel */ - chainId: number; - - /** Optional: Default challenge duration (in seconds) for new channels. Defaults to 0 if omitted. */ - challengeDuration?: bigint; -} -``` - -Configuration for initializing the `NitroliteClient`. - -### ContractAddresses - -```typescript -interface ContractAddresses { - custody: Address; // Custody contract address - adjudicator: Address; // Adjudicator contract address - guestAddress: Address; // Guest participant address - tokenAddress: Address; // Token address (zero address for ETH) -} -``` - -Addresses of contracts used by the Nitrolite SDK. - -## Channel Lifecycle Parameter Types - -### 1. Deposit Phase - -Deposit operations primarily use simple `bigint` parameters for amounts. - -### 2. Channel Creation - -```typescript -interface CreateChannelParams { - initialAllocationAmounts: [bigint, bigint]; // Initial allocation for [host, guest] - stateData?: Hex; // Application-specific data -} -``` - -Parameters for creating a new channel. - -### 3. Channel Operations - -```typescript -interface CheckpointChannelParams { - channelId: ChannelId; // Channel ID to checkpoint - candidateState: State; // State to checkpoint - proofStates?: State[]; // Optional proof states -} - -interface ChallengeChannelParams { - channelId: ChannelId; // Channel ID to challenge - candidateState: State; // State to submit as a challenge - proofStates?: State[]; // Optional proof states -} - -interface ResizeChannelParams { - resizeState: { - channelId: ChannelId; - stateData: Hex; - allocations: [Allocation, Allocation]; - version: bigint; - intent: StateIntent; - serverSignature: Signature; - }; - proofStates: State[]; -} -``` - -Parameters for channel operations. - -### 4. Channel Closing - -```typescript -interface CloseChannelParams { - stateData?: Hex; // Optional application data for the final state - finalState: { - channelId: ChannelId; // Channel ID to close - stateData: Hex; // Application-specific data - allocations: [Allocation, Allocation]; // Final allocations - version: bigint; // State version - serverSignature: Signature; // Server's signature on the state - }; -} -``` - -Parameters for collaboratively closing a channel. - -## Return Types - -### AccountInfo - -```typescript -interface AccountInfo { - available: bigint; // Available funds in the custody contract - channelCount: bigint; // Number of channels -} -``` - -Information about an account's funds in the custody contract. - -### PreparedTransaction - -```typescript -type PreparedTransaction = { - to: Address; // Target contract address - data?: Hex; // Contract call data - value?: bigint; // ETH value to send -}; -``` - -Represents the data needed to construct a transaction, used by the transaction preparer for Account Abstraction. - -## Type Usage By Channel Lifecycle Phase - -### 1. Deposit Phase - -```typescript -// Deposit -await client.deposit(amount: bigint): Promise - -// Check token details -const balance: bigint = await client.getTokenBalance() -const allowance: bigint = await client.getTokenAllowance() -``` - -### 2. Channel Creation Phase - -```typescript -// Create channel -const result: { - channelId: ChannelId; - initialState: State; - txHash: Hash -} = await client.createChannel({ - initialAllocationAmounts: [bigint, bigint], - stateData: Hex -}) -``` - -### 3. Channel Operation Phase - -```typescript -// Resize -await client.resizeChannel({ - resizeState: { - channelId: ChannelId, - stateData: Hex, - allocations: [Allocation, Allocation], - version: bigint, - intent: StateIntent, - serverSignature: Signature - }, - proofStates: State[] -}): Promise - -// Resize is structured as: -const resizeParams: ResizeChannelParams = { - resizeState: { - channelId, - stateData: '0x1234', - allocations: [ - { destination: addr1, token: tokenAddr, amount: 600000n }, - { destination: addr2, token: tokenAddr, amount: 400000n } - ], - version: 2n, // Incremented from previous - intent: StateIntent.RESIZE, - serverSignature: signature - }, - proofStates: [] -} -``` - -### 4. Channel Closing Phase - -```typescript -// Close -await client.closeChannel({ - finalState: { - channelId: ChannelId, - stateData: Hex, - allocations: [Allocation, Allocation], - version: bigint, - serverSignature: Signature - } -}): Promise -``` - -### 5. Withdrawal Phase - -```typescript -// Withdraw -await client.withdrawal(amount: bigint): Promise -``` - -## State Intent Lifecycle - -The `StateIntent` enum value determines how a state is interpreted: - -1. `StateIntent.INITIALIZE`: Used when creating a channel, defines the initial funding allocations -2. `StateIntent.OPERATE`: Used during normal operations, for application-specific state updates -3. `StateIntent.RESIZE`: Used when changing allocation amounts, e.g., adding funds to a channel -4. `StateIntent.FINALIZE`: Used when closing a channel, defines the final allocations - -Example of state progression: - -```typescript -// 1. Initial state (on channel creation) -const initialState = { - intent: StateIntent.INITIALIZE, - version: 0n, - data: '0x1234', - allocations: [ - { destination: userAddr, token: tokenAddr, amount: 700000n }, - { destination: guestAddr, token: tokenAddr, amount: 300000n } - ], - sigs: [userSig, guestSig] -}; - -// 2. Operation state (during application usage) -const operationalState = { - intent: StateIntent.OPERATE, - version: 1n, // Incremented - data: '0x5678', // Application data changed - allocations: [ - { destination: userAddr, token: tokenAddr, amount: 650000n }, // Balance changed - { destination: guestAddr, token: tokenAddr, amount: 350000n } // Balance changed - ], - sigs: [userSig, guestSig] -}; - -// 3. Resize state (adding funds) -const resizeState = { - intent: StateIntent.RESIZE, - version: 2n, - data: '0x5678', - allocations: [ - { destination: userAddr, token: tokenAddr, amount: 950000n }, // Added funds - { destination: guestAddr, token: tokenAddr, amount: 450000n } // Added funds - ], - sigs: [userSig, guestSig] -}; - -// 4. Final state (closing channel) -const finalState = { - intent: StateIntent.FINALIZE, - version: 3n, - data: '0x9ABC', - allocations: [ - { destination: userAddr, token: tokenAddr, amount: 930000n }, - { destination: guestAddr, token: tokenAddr, amount: 470000n } - ], - sigs: [userSig, guestSig] -}; -``` diff --git a/erc7824-docs/docs/nitrolite_rpc/index.md b/erc7824-docs/docs/nitrolite_rpc/index.md deleted file mode 100644 index 75d0d1f7d..000000000 --- a/erc7824-docs/docs/nitrolite_rpc/index.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -sidebar_position: 3 -title: NitroliteRPC -description: Overview of the NitroliteRPC, its core logic, and links to detailed API and type definitions. -keywords: [erc7824, statechannels, state channels, nitrolite, rpc, websockets, messaging, protocol] ---- - -import { Card, CardGrid } from '@site/src/components/Card'; -import MethodDetails from '@site/src/components/MethodDetails'; - -# NitroliteRPC - -The NitroliteRPC provides a secure, reliable real-time communication protocol for state channel applications. It enables off-chain message exchange, state updates, and channel management. This system is built around the `NitroliteRPC` class, which provides the foundational methods for message construction, signing, parsing, and verification. - - - - - - -## Core Logic: The `NitroliteRPC` Class - -The `NitroliteRPC` class is central to the RPC system. It offers a suite of static methods to handle the low-level details of the NitroliteRPC protocol. - -### Message Creation - - - - - - - - - -### Message Signing - -", description: "The RPC request object to sign." }, - { name: "signer", type: "MessageSigner", description: "An async function that takes a message string and returns a Promise (signature)." } - ]} - returns="Promise>" - example={`// Assuming 'request' is a NitroliteRPCRequest and 'signer' is a MessageSigner -const signedRequest = await NitroliteRPC.signRequestMessage(request, signer);`} -/> - -", description: "The RPC response object to sign." }, - { name: "signer", type: "MessageSigner", description: "An async function that takes a message string and returns a Promise (signature)." } - ]} - returns="Promise>" - example={`// Assuming 'response' is a NitroliteRPCResponse and 'signer' is a MessageSigner -const signedResponse = await NitroliteRPC.signResponseMessage(response, signer);`} -/> - -### Message Parsing & Validation - - - -These methods ensure that all communication adheres to the defined RPC structure and security requirements. - -## Generic Message Structure - -The `NitroliteRPC` class operates on messages adhering to the following general structures. For precise details on each field and for specific message types, please refer to the [RPC Type Definitions](./rpc_types). - -```typescript -// Generic Request message structure -{ - "req": [requestId, method, params, timestamp], // Core request tuple - "int"?: Intent, // Optional intent for state changes - "acc"?: AccountID, // Optional account scope (channel/app ID) - "sig": [signature] // Array of signatures -} - -// Generic Response message structure -{ - "res": [requestId, method, dataPayload, timestamp], // Core response tuple - "acc"?: AccountID, // Optional account scope - "int"?: Intent, // Optional intent - "sig"?: [signature] // Optional signatures for certain response types -} -``` - -## Next Steps - -Dive deeper into the specifics of the RPC system: - - - - - \ No newline at end of file diff --git a/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md b/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md deleted file mode 100644 index 39e17ed53..000000000 --- a/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md +++ /dev/null @@ -1,403 +0,0 @@ ---- -sidebar_position: 3 -title: RPC Message Creation API -description: Detailed reference for functions that create specific, signed Nitrolite RPC request messages. -keywords: [erc7824, statechannels, state channels, nitrolite, rpc, api, message creation, sdk] ---- - -import MethodDetails from '@site/src/components/MethodDetails'; - -# RPC Message Creation API - -The functions detailed below are part of the `@erc7824/nitrolite` SDK and provide a convenient way to create fully formed, signed, and stringified JSON RPC request messages. These messages are ready for transmission over a WebSocket or any other transport layer communicating with a Nitrolite-compatible broker. - -Each function typically takes a `MessageSigner` (a function you provide to sign messages, usually integrating with a user's wallet) and other relevant parameters, then returns a `Promise` which resolves to the JSON string of the signed RPC message. - -## Authentication - -These functions are used for the client authentication flow with the broker. The typical sequence is: -1. Client sends an `auth_request` (using `createAuthRequestMessage`). -2. Broker responds with an `auth_challenge`. -3. Client signs the challenge and sends an `auth_verify` (using `createAuthVerifyMessageFromChallenge` or `createAuthVerifyMessage`). -4. Broker responds with `auth_success` or `auth_failure`. - -```mermaid -sequenceDiagram - participant Client - participant Broker - - Client->>Broker: auth_request (via createAuthRequestMessage) - Broker-->>Client: auth_challenge (response) - Client->>Broker: auth_verify (via createAuthVerifyMessage or createAuthVerifyMessageFromChallenge) - Broker-->>Client: auth_success / auth_failure (response) -``` - - - - - - - -## General & Keep-Alive - -Functions for general RPC interactions like keep-alive messages. - - - -## Query Operations - -Functions for retrieving information from the broker. - - - - - - - - - -## Application Session Management - -Functions for creating and closing application sessions (state channels). - - - - - -## Application-Specific Messaging - -Function for sending messages within an active application session. - - - -## Ledger Channel Management - -Functions for managing the underlying ledger channels (direct channels with the broker). - - - - - -## Advanced: Creating a Local Signer for Development - -To begin, you'll often need a fresh key pair (private key, public key, and address). The `generateKeyPair` utility can be used for this: - -```typescript -import { ethers } from "ethers"; // Make sure ethers is installed - -// Definition (as provided in your example) -interface CryptoKeypair { - privateKey: string; - publicKey: string; - address: string; -} - -export const generateKeyPair = async (): Promise => { - try { - const wallet = ethers.Wallet.createRandom(); - const privateKeyHash = ethers.utils.keccak256(wallet.privateKey); - const walletFromHashedKey = new ethers.Wallet(privateKeyHash); - - return { - privateKey: privateKeyHash, - publicKey: walletFromHashedKey.publicKey, - address: walletFromHashedKey.address, - }; - } catch (error) { - console.error('Error generating keypair, using fallback:', error); - const randomHex = ethers.utils.randomBytes(32); - const privateKey = ethers.utils.keccak256(randomHex); - const wallet = new ethers.Wallet(privateKey); - - return { - privateKey: privateKey, - publicKey: wallet.publicKey, - address: wallet.address, - }; - } -}; - -// Usage: -async function main() { - const keyPair = await generateKeyPair(); - console.log("Generated Private Key:", keyPair.privateKey); - console.log("Generated Address:", keyPair.address); - // Store keyPair.privateKey securely if you need to reuse this signer -} -``` -This function creates a new random wallet, hashes its private key for deriving a new one (a common pattern for deterministic key generation or added obfuscation, though the security implications depend on the exact use case), and returns the private key, public key, and address. - -### Creating a Signer from a Private Key - -Once you have a private key (either generated as above or from a known development account), you can create a `MessageSigner` compatible with the RPC message creation functions. The `MessageSigner` interface typically expects an asynchronous `sign` method. - -```typescript -import { ethers } from "ethers"; -import { Hex } from "viem"; // Assuming Hex type is from viem or similar - -// Definitions (as provided in your example) -type RequestData = unknown; // Placeholder for actual request data type -type ResponsePayload = unknown; // Placeholder for actual response payload type - -interface WalletSigner { - publicKey: string; - address: Hex; - sign: (payload: RequestData | ResponsePayload) => Promise; -} - -export const createEthersSigner = (privateKey: string): WalletSigner => { - try { - const wallet = new ethers.Wallet(privateKey); - - return { - publicKey: wallet.publicKey, - address: wallet.address as Hex, - sign: async (payload: RequestData | ResponsePayload): Promise => { - try { - // The NitroliteRPC.hashMessage method should ideally be used here - // to ensure the exact same hashing logic as the SDK internals. - // For demonstration, using a generic hashing approach: - const messageToSign = JSON.stringify(payload); - const messageHash = ethers.utils.id(messageToSign); // ethers.utils.id performs keccak256 - const messageBytes = ethers.utils.arrayify(messageHash); - - const flatSignature = await wallet._signingKey().signDigest(messageBytes); - const signature = ethers.utils.joinSignature(flatSignature); - return signature as Hex; - } catch (error) { - console.error('Error signing message:', error); - throw error; - } - }, - }; - } catch (error) { - console.error('Error creating ethers signer:', error); - throw error; - } -}; - -// Usage: -async function setupSigner() { - const keyPair = await generateKeyPair(); // Or use a known private key - const localSigner = createEthersSigner(keyPair.privateKey); - - // Now 'localSigner' can be passed to the RPC message creation functions: - // const authRequest = await createAuthRequestMessage(localSigner.sign, localSigner.address as Address); - // console.log("Auth Request with local signer:", authRequest); -} -``` - -**Important Considerations for `createEthersSigner`:** -* **Hashing Consistency:** The `sign` method within `createEthersSigner` must hash the payload in a way that is **identical** to how the `NitroliteRPC` class (specifically `NitroliteRPC.hashMessage`) expects messages to be hashed before signing. The example above uses `ethers.utils.id(JSON.stringify(payload))`. It's crucial to verify if the SDK's internal hashing uses a specific message prefix (e.g., EIP-191 personal_sign prefix) or a different serialization method. If the SDK does *not* use a standard EIP-191 prefix, or uses a custom one, your local signer's hashing logic must replicate this exactly for signatures to be valid. Using `NitroliteRPC.hashMessage(payload)` directly (if `payload` matches the `NitroliteRPCMessage` structure) is the safest way to ensure consistency. -* **Type Compatibility:** Ensure the `Address` type expected by functions like `createAuthRequestMessage` is compatible with `localSigner.address`. The example uses `localSigner.address as Address` assuming `Address` is `0x${string}`. -* **Error Handling:** The provided examples include basic error logging. Robust applications should implement more sophisticated error handling. diff --git a/erc7824-docs/docs/nitrolite_rpc/rpc_types.md b/erc7824-docs/docs/nitrolite_rpc/rpc_types.md deleted file mode 100644 index 2848c3156..000000000 --- a/erc7824-docs/docs/nitrolite_rpc/rpc_types.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -sidebar_position: 4 # Adjusted sidebar_position if message_creation_api is 3 -title: RPC Type Definitions -description: Comprehensive type definitions for the Nitrolite RPC protocol. -keywords: [erc7824, statechannels, state channels, nitrolite, rpc, types, typescript, protocol, api, definitions] ---- - -# Nitrolite RPC Type Definitions - -This page provides a comprehensive reference for all TypeScript types, interfaces, and enums used by the Nitrolite RPC system, as defined in the `@erc7824/nitrolite` SDK. These definitions are crucial for understanding the structure of messages exchanged with the Nitrolite broker. - -## Core Types - -These are fundamental types used throughout the RPC system. - -### `RequestID` -A unique identifier for an RPC request. Typically a number. -```typescript -export type RequestID = number; -``` - -### `Timestamp` -Represents a Unix timestamp in milliseconds. Used for message ordering and security. -```typescript -export type Timestamp = number; -``` - -### `AccountID` -A unique identifier for a channel or application session, represented as a hexadecimal string. -```typescript -export type AccountID = Hex; // from 'viem' -``` - -### `Intent` -Represents the allocation intent change as an array of big integers. This is used to specify how funds should be re-distributed in a state update. -```typescript -export type Intent = bigint[]; -``` - -## Message Payloads - -These types define the core data arrays within RPC messages. - -### `RequestData` -The structured data payload within a request message. -```typescript -export type RequestData = [RequestID, string, any[], Timestamp?]; -``` -- `RequestID`: The unique ID of this request. -- `string`: The name of the RPC method being called. -- `any[]`: An array of parameters for the method. -- `Timestamp?`: An optional timestamp for when the request was created. - -### `ResponseData` -The structured data payload within a successful response message. -```typescript -export type ResponseData = [RequestID, string, any[], Timestamp?]; -``` -- `RequestID`: The ID of the original request this response is for. -- `string`: The name of the original RPC method. -- `any[]`: An array containing the result(s) of the method execution. -- `Timestamp?`: An optional timestamp for when the response was created. - -### `NitroliteRPCErrorDetail` -Defines the structure of the error object within an error response. -```typescript -export interface NitroliteRPCErrorDetail { - error: string; -} -``` -- `error`: A string describing the error that occurred. - -### `ErrorResponseData` -The structured data payload for an error response message. -```typescript -export type ErrorResponseData = [RequestID, "error", [NitroliteRPCErrorDetail], Timestamp?]; -``` -- `RequestID`: The ID of the original request this error is for. -- `"error"`: A literal string indicating this is an error response. -- `[NitroliteRPCErrorDetail]`: An array containing a single `NitroliteRPCErrorDetail` object. -- `Timestamp?`: An optional timestamp for when the error response was created. - -### `ResponsePayload` -A union type representing the payload of a response, which can be either a success (`ResponseData`) or an error (`ErrorResponseData`). -```typescript -export type ResponsePayload = ResponseData | ErrorResponseData; -``` - -## Message Envelopes - -These interfaces define the overall structure of messages sent over the wire. - -### `NitroliteRPCMessage` -The base wire format for Nitrolite RPC messages. -```typescript -export interface NitroliteRPCMessage { - req?: RequestData; - res?: ResponsePayload; - int?: Intent; - sig?: Hex[]; -} -``` -- `req?`: The request payload, if this is a request message. -- `res?`: The response payload, if this is a response message. -- `int?`: Optional allocation intent change. -- `sig?`: Optional array of cryptographic signatures (hex strings). - -## Parsing Results - -### `ParsedResponse` -Represents the result of parsing an incoming Nitrolite RPC response message. -```typescript -export interface ParsedResponse { - isValid: boolean; - error?: string; - isError?: boolean; - requestId?: RequestID; - method?: string; - data?: any[] | NitroliteRPCErrorDetail; - acc?: AccountID; - int?: Intent; - timestamp?: Timestamp; -} -``` -- `isValid`: `true` if the message was successfully parsed and passed basic structural validation. -- `error?`: If `isValid` is `false`, contains a description of the parsing or validation error. -- `isError?`: `true` if the parsed response represents an error (i.e., `method === "error"`). Undefined if `isValid` is `false`. -- `requestId?`: The `RequestID` from the response payload. Undefined if the structure is invalid. -- `method?`: The method name from the response payload. Undefined if the structure is invalid. -- `data?`: The extracted data payload (result array for success, `NitroliteRPCErrorDetail` object for error). Undefined if the structure is invalid or the error payload is malformed. -- `acc?`: The `AccountID` from the message envelope, if present. -- `int?`: The `Intent` from the message envelope, if present. -- `timestamp?`: The `Timestamp` from the response payload. Undefined if the structure is invalid. - -## Request Parameter Structures - -These interfaces define the expected parameters for specific RPC methods. - -### `AppDefinition` -Defines the structure of an application's configuration. -```typescript -export interface AppDefinition { - protocol: string; - participants: Hex[]; - weights: number[]; - quorum: number; - challenge: number; - nonce?: number; -} -``` -- `protocol`: The protocol identifier or name for the application logic (e.g., `"NitroRPC/0.2"`). -- `participants`: An array of participant addresses (Ethereum addresses as `Hex`) involved in the application. -- `weights`: An array representing the relative weights or stakes of participants. Order corresponds to the `participants` array. -- `quorum`: The number/percentage of participants (based on weights) required to reach consensus. -- `challenge`: A parameter related to the challenge period or mechanism (e.g., duration in seconds). -- `nonce?`: An optional unique number (nonce) used to ensure the uniqueness of the application instance and prevent replay attacks. - -### `CreateAppSessionRequest` -Parameters for the `create_app_session` RPC method. -```typescript -export interface CreateAppSessionRequest { - definition: AppDefinition; - token: Hex; - allocations: bigint[]; -} -``` -- `definition`: The `AppDefinition` object detailing the application being created. -- `token`: The `Hex` address of the ERC20 token contract used for allocations within this application session. -- `allocations`: An array of `bigint` representing the initial allocation distribution among participants. The order corresponds to the `participants` array in the `definition`. - -Example: -```json -{ - "definition": { - "protocol": "NitroRPC/0.2", - "participants": [ - "0xAaBbCcDdEeFf0011223344556677889900aAbBcC", - "0x00112233445566778899AaBbCcDdEeFf00112233" - ], - "weights": [100, 0], // Example: Participant 1 has 100% weight - "quorum": 100, // Example: 100% quorum needed - "challenge": 86400, // Example: 1 day challenge period - "nonce": 12345 - }, - "token": "0xTokenContractAddress00000000000000000000", - "allocations": ["1000000000000000000", "0"] // 1 Token for P1, 0 for P2 (as strings for bigint) -} -``` - -### `CloseAppSessionRequest` -Parameters for the `close_app_session` RPC method. -```typescript -export interface CloseAppSessionRequest { - app_id: Hex; - allocations: bigint[]; -} -``` -- `app_id`: The unique `AccountID` (as `Hex`) of the application session to be closed. -- `allocations`: An array of `bigint` representing the final allocation distribution among participants upon closing. Order corresponds to the `participants` array in the application's definition. - -### `ResizeChannel` -Parameters for the `resize_channel` RPC method. -```typescript -export interface ResizeChannel { - channel_id: Hex; - participant_change: bigint; - funds_destination: Hex; -} -``` -- `channel_id`: The unique `AccountID` (as `Hex`) of the direct ledger channel to be resized. -- `participant_change`: The `bigint` amount by which the participant's allocation in the channel should change (positive to add funds, negative to remove). -- `funds_destination`: The `Hex` address where funds will be sent if `participant_change` is negative (withdrawal), or the source of funds if positive (though typically handled by prior on-chain deposit). - -## Function Types (Signers & Verifiers) - -These types define the signatures for functions used in cryptographic operations. - -### `MessageSigner` -A function that signs a message payload. -```typescript -export type MessageSigner = (payload: RequestData | ResponsePayload) => Promise; -``` -- Takes a `RequestData` or `ResponsePayload` object (the array part of the message). -- Returns a `Promise` that resolves to the cryptographic signature as a `Hex` string. - -### `SingleMessageVerifier` -A function that verifies a single message signature. -```typescript -export type SingleMessageVerifier = ( - payload: RequestData | ResponsePayload, - signature: Hex, - address: Address // from 'viem' -) => Promise; -``` -- Takes the `RequestData` or `ResponsePayload` object, the `Hex` signature, and the expected signer's `Address`. -- Returns a `Promise` that resolves to `true` if the signature is valid for the given payload and address, `false` otherwise. - -## Usage Examples - -### Creating Message Payloads and Envelopes -```typescript -// Example Request Payload (for a 'ping' method) -const pingRequestData: RequestData = [1, "ping", []]; // Assuming timestamp is added by sender utility - -// Example Request Envelope -const pingRequestMessage: NitroliteRPCMessage = { - req: pingRequestData, - // sig: ["0xSignatureIfPreSigned..."] // Signature added by signing utility -}; - -// Example Application-Specific Request -const appActionData: RequestData = [2, "message", [{ move: "rock" }], Date.now()]; -const appActionMessage: ApplicationRPCMessage = { - sid: "0xAppSessionId...", - req: appActionData, - // sig: ["0xSignature..."] -}; - -// Example Successful Response Payload -const pongResponseData: ResponseData = [1, "ping", ["pong"], Date.now()]; - -// Example Error Detail -const errorDetail: NitroliteRPCErrorDetail = { error: "Method parameters are invalid." }; - -// Example Error Response Payload -const errorResponseData: ErrorResponseData = [2, "error", [errorDetail], Date.now()]; - -// Example Response Envelope (Success) -const successResponseEnvelope: NitroliteRPCMessage = { - res: pongResponseData, -}; -``` - -### Working with Signers (Conceptual) -```typescript -// Conceptual: How a MessageSigner might be used -async function signAndSend(payload: RequestData, signer: MessageSigner, sendMessageToServer: (msg: string) => void) { - const signature = await signer(payload); - const message: NitroliteRPCMessage = { - req: payload, - sig: [signature] - }; - sendMessageToServer(JSON.stringify(message)); -} -``` - -## Implementation Considerations - -When working with these types: - -1. **Serialization**: Messages are typically serialized to JSON strings for transmission (e.g., over WebSockets). -2. **Signing**: Payloads (`req` or `res` arrays) are what get signed, not the entire envelope. The resulting signature is then added to the `sig` field of the `NitroliteRPCMessage` envelope. -3. **Validation**: Always validate the structure and types of incoming messages against these definitions, preferably using utilities provided by the SDK. -4. **Error Handling**: Properly check for `isError` in `ParsedResponse` and use `NitroliteErrorCode` to understand the nature of failures. -5. **BigInts**: Note the use of `bigint` for `Intent` and allocation amounts. Ensure your environment and serialization/deserialization logic handle `bigint` correctly (e.g., converting to/from strings for JSON). -6. **Hex Strings**: Types like `AccountID`, `Hex` (for signatures, token addresses) imply hexadecimal string format (e.g., `"0x..."`). diff --git a/erc7824-docs/docs/quick_start/application_session.md b/erc7824-docs/docs/quick_start/application_session.md deleted file mode 100644 index 08f1e1e0c..000000000 --- a/erc7824-docs/docs/quick_start/application_session.md +++ /dev/null @@ -1,1071 +0,0 @@ ---- -sidebar_position: 7 -title: Create Application Sessions -description: Create and manage off-chain application sessions to interact with ClearNodes. -keywords: [erc7824, nitrolite, application session, state channels, app session] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Create Application Sessions - -After connecting to a ClearNode and checking your channel balances, you can create application sessions to interact with specific applications on the state channel network. Application sessions allow you to perform off-chain transactions and define custom behavior for your interactions. - -## Understanding Application Sessions - -```mermaid -graph TD - A[Create Application Session] --> B[Session Active] - B --> C[Off-Chain Transactions] - C --> D[Close Session] -``` - -Application sessions in Nitrolite allow you to: - -- Create isolated environments for specific interactions -- Define rules for off-chain transactions -- Specify how funds are allocated between participants -- Implement custom application logic and state management - -An application session serves as a mechanism to track and manage interactions between participants, with the ClearNode acting as a facilitator. - -## Creating an Application Session - -To create an application session, you'll use the `createAppSessionMessage` helper from NitroliteRPC. Here's how to do it: - - - - -```javascript -import { createAppSessionMessage, parseRPCResponse, MessageSigner, CreateAppSessionRPCParams } from '@erc7824/nitrolite'; -import { useCallback } from 'react'; -import { Address } from 'viem'; - -function useCreateApplicationSession() { - const createApplicationSession = useCallback( - async ( - signer: MessageSigner, - sendRequest: (message: string) => Promise, - participantA: Address, - participantB: Address, - amount: string, - ) => { - try { - // Define the application parameters - const appDefinition = { - protocol: 'nitroliterpc', - participants: [participantA, participantB], - weights: [100, 0], // Weight distribution for consensus - quorum: 100, // Required consensus percentage - challenge: 0, // Challenge period - nonce: Date.now(), // Unique identifier - }; - - // Define allocations with asset type instead of token address - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: amount, - }, - { - participant: participantB, - asset: 'usdc', - amount: '0', - }, - ]; - - // Create a signed message using the createAppSessionMessage helper - const signedMessage = await createAppSessionMessage( - signer, - [ - { - definition: appDefinition, - allocations: allocations, - }, - ] - ); - - // Send the signed message to the ClearNode - const response = await sendRequest(signedMessage); - - // Handle the response - if (response.app_session_id) { - // Store the app session ID for future reference - localStorage.setItem('app_session_id', response.app_session_id); - return { success: true, app_session_id: response.app_session_id, response }; - } else { - return { success: true, response }; - } - } catch (error) { - console.error('Error creating application session:', error); - return { - success: false, - error: error instanceof Error - ? error.message - : 'Unknown error during session creation', - }; - } - }, - [] - ); - - return { createApplicationSession }; -} - -// Usage example -function MyComponent() { - const { createApplicationSession } = useCreateApplicationSession(); - - const handleCreateSession = async () => { - // Define your WebSocket send wrapper - const sendRequest = async (payload: string) => { - return new Promise((resolve, reject) => { - // Assuming ws is your WebSocket connection - const handleMessage = (event) => { - try { - const message = parseRPCResponse(event.data); - if (message.method === RPCMethod.CreateAppSession) { - ws.removeEventListener('message', handleMessage); - resolve(message.params); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - ws.addEventListener('message', handleMessage); - ws.send(payload); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeEventListener('message', handleMessage); - reject(new Error('App session creation timeout')); - }, 10000); - }); - }; - - const result = await createApplicationSession( - walletSigner, // Your signer object - sendRequest, // Function to send the request - '0xYourAddress', // Your address - '0xOtherAddress', // Other participant's address - '100', // Amount - ); - - if (result.success) { - console.log(`Application session created with ID: ${result.app_session_id}`); - } else { - console.error(`Failed to create application session: ${result.error}`); - } - }; - - return ( - - ); -} -``` - - - - -```typescript -// app-session.service.ts -import { Injectable } from '@angular/core'; -import { createAppSessionMessage } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; -import { BehaviorSubject, Observable, from } from 'rxjs'; -import { tap, catchError } from 'rxjs/operators'; - -@Injectable({ - providedIn: 'root' -}) -export class AppSessionService { - private webSocket: WebSocket | null = null; - private appIdSubject = new BehaviorSubject(null); - - public appId$ = this.appIdSubject.asObservable(); - - constructor() { - // Retrieve app ID from storage if available - const storedAppId = localStorage.getItem('app_session_id'); - if (storedAppId) { - this.appIdSubject.next(storedAppId); - } - } - - public setWebSocket(ws: WebSocket): void { - this.webSocket = ws; - } - - public createApplicationSession( - signer: any, - participantA: string, - participantB: string, - amount: string, - ): Observable { - if (!this.webSocket) { - throw new Error('WebSocket connection is not established'); - } - - return from(this.createAppSessionAsync( - signer, - participantA, - participantB, - amount, - )).pipe( - tap(result => { - if (result.success && result.app_session_id) { - localStorage.setItem('app_session_id', result.app_session_id); - this.appIdSubject.next(result.app_session_id); - } - }), - catchError(error => { - console.error('Error creating application session:', error); - throw error; - }) - ); - } - - private async createAppSessionAsync( - signer: any, - participantA: string, - participantB: string, - amount: string, - ): Promise { - try { - - // Define the application parameters - const appDefinition = { - protocol: 'nitroliterpc', - participants: [participantA, participantB], - weights: [100, 0], - quorum: 100, - challenge: 0, - nonce: Date.now(), - }; - - // Define the allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: amount, - }, - { - participant: participantB, - asset: 'usdc', - amount: '0', - }, - ]; - - // Create message signer function - const messageSigner = async (payload: any) => { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const signature = await signer.signMessage(messageBytes); - return signature; - }; - - // Create the signed message - const signedMessage = await createAppSessionMessage( - messageSigner, - [ - { - definition: appDefinition, - allocations: allocations, - }, - ] - ); - - // Send the message and wait for response - return await this.sendRequest(signedMessage); - } catch (error) { - console.error('Error in createAppSessionAsync:', error); - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }; - } - } - - private sendRequest(payload: string): Promise { - return new Promise((resolve, reject) => { - if (!this.webSocket) { - reject(new Error('WebSocket not connected')); - return; - } - - const handleMessage = (event: MessageEvent) => { - try { - const message = JSON.parse(event.data); - if (message.res && message.res[1] === 'create_app_session') { - this.webSocket?.removeEventListener('message', handleMessage); - resolve({ - success: true, - app_session_id: message.res[2]?.[0]?.app_session_id || null, - status: message.res[2]?.[0]?.status || "open", - response: message.res[2] - }); - } - - if (message.err) { - this.webSocket?.removeEventListener('message', handleMessage); - reject(new Error(`Error: ${message.err[1]} - ${message.err[2]}`)); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - this.webSocket.addEventListener('message', handleMessage); - this.webSocket.send(payload); - - // Set timeout to prevent hanging - setTimeout(() => { - this.webSocket?.removeEventListener('message', handleMessage); - reject(new Error('App session creation timeout')); - }, 10000); - }); - } -} - -// app-session.component.ts -import { Component, OnInit } from '@angular/core'; -import { AppSessionService } from './app-session.service'; - -@Component({ - selector: 'app-session-creator', - template: ` -
-

Create Application Session

-
- Current Application Session ID: {{ appId }} -
- -
- {{ error }} -
-
- `, - styles: [` - .app-session-container { - margin: 20px; - padding: 15px; - border: 1px solid #eee; - border-radius: 5px; - } - .error-message { - color: red; - margin-top: 10px; - } - `] -}) -export class AppSessionComponent implements OnInit { - appId: string | null = null; - isCreating = false; - error: string | null = null; - - constructor(private appSessionService: AppSessionService) {} - - ngOnInit(): void { - // Subscribe to app ID changes - this.appSessionService.appId$.subscribe(id => { - this.appId = id; - }); - - // Initialize WebSocket (implementation would depend on your setup) - const ws = new WebSocket('wss://your-clearnode-endpoint'); - ws.onopen = () => { - this.appSessionService.setWebSocket(ws); - }; - } - - createAppSession(): void { - this.isCreating = true; - this.error = null; - - // Sample values - in a real app you'd get these from input fields or a service - const participantA = '0xYourAddress'; - const participantB = '0xOtherAddress'; - const amount = '1000000'; // 1 USDC with 6 decimals - - // Assuming you have access to a signer (e.g., from MetaMask) - const signer = window.ethereum && new ethers.providers.Web3Provider(window.ethereum).getSigner(); - - if (!signer) { - this.error = 'No wallet connected'; - this.isCreating = false; - return; - } - - this.appSessionService.createApplicationSession( - signer, - participantA, - participantB, - amount - ).subscribe({ - next: (result) => { - console.log('App session created:', result); - this.isCreating = false; - }, - error: (err) => { - this.error = `Failed to create application session: ${err.message}`; - this.isCreating = false; - } - }); - } -} -``` - -
- - -```javascript - - - - - - -``` - - - - -```javascript -import { createAppSessionMessage } from '@erc7824/nitrolite'; -import WebSocket from 'ws'; -import { ethers } from 'ethers'; - -/** - * Create an app session - * @param {string} participantA - First participant's address - * @param {string} participantB - Second participant's address - * @param {WebSocket} ws - WebSocket connection to the ClearNode - * @param {object} wallet - Ethers wallet for signing - * @returns {Promise} The app session ID - */ -async function createAppSession(participantA, participantB, ws, wallet) { - try { - console.log(`Creating app session between ${participantA} and ${participantB}`); - - // Message signer function - const messageSigner = async (payload) => { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = wallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }; - - // Create app definition - const appDefinition = { - protocol: "nitroliterpc", - participants: [participantA, participantB], - weights: [100, 0], - quorum: 100, - challenge: 0, - nonce: Date.now(), - }; - - // Define the allocations with asset type (e.g., 1 USDC with 6 decimals) - const amount = '1000000'; - - // Define allocations - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: amount, - }, - { - participant: participantB, - asset: 'usdc', - amount: '0', - }, - ]; - - // Create the signed message - const signedMessage = await createAppSessionMessage( - messageSigner, - [ - { - definition: appDefinition, - allocations: allocations, - }, - ] - ); - - // Send the message and wait for response - return new Promise((resolve, reject) => { - // Create a one-time message handler for the app session response - const handleAppSessionResponse = (data) => { - try { - const rawData = typeof data === 'string' ? data : data.toString(); - const message = JSON.parse(rawData); - - console.log('Received app session creation response:', message); - - // Check if this is an app session response - if (message.res && - (message.res[1] === 'create_app_session' || - message.res[1] === 'app_session_created')) { - // Remove the listener once we get the response - ws.removeListener('message', handleAppSessionResponse); - - // Extract app session ID from response - const appSessionId = message.res[2]?.[0]?.app_session_id; - if (!appSessionId) { - reject(new Error('Failed to get app session ID from response')); - return; - } - - resolve(appSessionId); - } - - // Check for error responses - if (message.err) { - ws.removeListener('message', handleAppSessionResponse); - reject(new Error(`Error ${message.err[1]}: ${message.err[2]}`)); - } - } catch (error) { - console.error('Error handling app session response:', error); - } - }; - - // Add the message handler - ws.on('message', handleAppSessionResponse); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeListener('message', handleAppSessionResponse); - reject(new Error('App session creation timeout')); - }, 10000); - - // Send the signed message - ws.send(signedMessage); - }); - } catch (error) { - console.error('Error creating app session:', error); - throw error; - } -} - -// Usage example -const participantA = '0x1234...'; // Your address -const participantB = '0x5678...'; // Other participant's address - -// Assuming you have a WebSocket connection and wallet initialized -createAppSession(participantA, participantB, ws, wallet) - .then(appSessionId => { - console.log(`Application session created with ID: ${appSessionId}`); - // Store the app session ID for future reference - }) - .catch(error => { - console.error('Failed to create application session:', error); - }); -``` - - - - -```javascript -/** - * Nitrolite app sessions manager for game rooms - */ -import { createAppSessionMessage } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// Map to store app sessions by room ID -const roomAppSessions = new Map(); - -/** - * Create an app session for a game room - * @param {string} roomId - Room ID - * @param {string} participantA - First player's address - * @param {string} participantB - Second player's address - * @param {WebSocket} ws - WebSocket connection to the ClearNode - * @param {object} wallet - Ethers wallet for signing - * @returns {Promise} The app session ID - */ -export async function createAppSession(roomId, participantA, participantB, ws, wallet) { - try { - console.log(`Creating app session for room ${roomId}`); - - // Get the server's address - const serverAddress = wallet.address; - - // Create app definition with server as a participant - // In this setup, the server acts as a referee/facilitator - const appDefinition = { - protocol: "app_nitrolite_v0", - participants: [participantA, participantB, serverAddress], - weights: [0, 0, 100], // Server has full control - quorum: 100, - challenge: 0, - nonce: Date.now(), - }; - - // Define allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: '0', - }, - { - participant: participantB, - asset: 'usdc', - amount: '0', - }, - { - participant: serverAddress, - asset: 'usdc', - amount: '0', - }, - ]; - - // Message signer function - const messageSigner = async (payload) => { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = wallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }; - - // Create the signed message - const signedMessage = await createAppSessionMessage( - messageSigner, - [ - { - definition: appDefinition, - allocations: allocations, - }, - ] - ); - - // Send the message and wait for response - return new Promise((resolve, reject) => { - // Create a one-time message handler for the app session response - const handleAppSessionResponse = (data) => { - try { - const rawData = typeof data === 'string' ? data : data.toString(); - const message = JSON.parse(rawData); - - console.log('Received app session creation response:', message); - - // Check if this is an app session response - if (message.res && - (message.res[1] === 'create_app_session' || - message.res[1] === 'app_session_created')) { - // Remove the listener once we get the response - ws.removeListener('message', handleAppSessionResponse); - - // Extract app session ID from response - const appSessionId = message.res[2]?.[0]?.app_session_id; - if (!appSessionId) { - reject(new Error('Failed to get app session ID from response')); - return; - } - - // Get status from response - const status = message.res[2]?.[0]?.status || "open"; - - // Store the app session for this room - roomAppSessions.set(roomId, { - appSessionId, - status, - participantA, - participantB, - serverAddress, - createdAt: Date.now() - }); - - resolve(appSessionId); - } - - // Check for error responses - if (message.err) { - ws.removeListener('message', handleAppSessionResponse); - reject(new Error(`Error ${message.err[1]}: ${message.err[2]}`)); - } - } catch (error) { - console.error('Error handling app session response:', error); - } - }; - - // Add the message handler - ws.on('message', handleAppSessionResponse); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeListener('message', handleAppSessionResponse); - reject(new Error('App session creation timeout')); - }, 10000); - - // Send the signed message - ws.send(signedMessage); - }); - } catch (error) { - console.error(`Error creating app session for room ${roomId}:`, error); - throw error; - } -} - -/** - * Get the app session for a room - * @param {string} roomId - Room ID - * @returns {Object|null} The app session or null if not found - */ -export function getAppSession(roomId) { - return roomAppSessions.get(roomId) || null; -} - -/** - * Check if a room has an app session - * @param {string} roomId - Room ID - * @returns {boolean} Whether the room has an app session - */ -export function hasAppSession(roomId) { - return roomAppSessions.has(roomId); -} -``` - - -
- -## Key Components of an Application Session - -When creating an application session, you need to define several key components: - -| Component | Description | Example | -|-----------|-------------|---------| -| **Protocol** | Identifier for the application protocol | `"nitroliterpc"` | -| **Participants** | Array of participant addresses | `[userAddress, counterpartyAddress]` | -| **Weights** | Weight distribution for consensus | `[100, 0]` for user-controlled, `[50, 50]` for equal | -| **Quorum** | Required percentage for consensus | Usually `100` for full consensus | -| **Challenge** | Time period for disputing state | `0` for no challenge period | -| **Nonce** | Unique identifier | Typically `Date.now()` | -| **Allocations** | Array of allocation objects with: | `[{ participant: "0xAddress", asset: "usdc", amount: "100" }]` | -| | - participant: Address of the participant | | -| | - asset: Asset identifier (e.g., "usdc", "eth") | | -| | - amount: String amount with precision | | - -### Response Components - -When a ClearNode responds to your application session creation request, it provides: - -| Component | Description | Example | -|-----------|-------------|---------| -| **app_session_id** | Unique identifier for the application session | `"0x0ac588b2924edbbbe34bb4c51d089771bd7bd7018136c8c4317624112a8c9f79"` | -| **status** | Current state of the application session | `"open"` | - -## Understanding the Response - -When you create an application session, the ClearNode responds with information about the created session: - -```javascript -// Example response -{ - "res": [ - 2, // Request ID - "create_app_session", // Method name - [ - { - "app_session_id": "0x0ac588b2924edbbbe34bb4c51d089771bd7bd7018136c8c4317624112a8c9f79", // Session ID - "status": "open" - } - ], - 1631234567890 // Timestamp - ], - "sig": ["0xSignature"] -} -``` - -The most important part of the response is the `app_session_id`, which you'll need for all future interactions with this application session. - -## Application Session Use Cases - -Application sessions can be used for various scenarios: - -1. **Peer-to-peer payments**: Direct token transfers between users -2. **Gaming**: Turn-based games with state transitions -3. **Content access**: Pay-per-use access to digital content -4. **Service payments**: Metered payment for API or service usage -5. **Multi-party applications**: Applications involving more than two participants - -## Best Practices - -When working with application sessions, follow these best practices: - -1. **Store the app_session_id securely**: You'll need it for all session-related operations -2. **Verify session creation**: Check for successful creation before proceeding -3. **Handle timeouts**: Implement proper timeout handling for session creation -4. **Clean up listeners**: Always remove message event listeners to prevent memory leaks -5. **Handle errors gracefully**: Provide clear error messages to users - -## Next Steps - -After creating an application session, you can: - -1. Use the session for application-specific transactions -2. [Check your channel balances](balances) to monitor funds -3. [Close the application session](close_session) when you're done - -For advanced use cases, see our detailed documentation on application workflows. \ No newline at end of file diff --git a/erc7824-docs/docs/quick_start/balances.md b/erc7824-docs/docs/quick_start/balances.md deleted file mode 100644 index 5245797c9..000000000 --- a/erc7824-docs/docs/quick_start/balances.md +++ /dev/null @@ -1,396 +0,0 @@ ---- -sidebar_position: 6 -title: Channel Asset Management -description: Monitor off-chain balances in your active state channels using NitroliteRPC. -keywords: [erc7824, nitrolite, balances, off-chain, ledger balances, clearnode] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Channel Asset Management - -After connecting to a ClearNode, you'll need to monitor the off-chain balances in your state channels. This guide explains how to retrieve and work with off-chain balance information using the NitroliteRPC protocol. - -## Understanding Off-Chain Balances - -Off-chain balances in Nitrolite represent: - -- Your current funds in the state channel -- Balances that update in real-time as transactions occur -- The source of truth for application operations -- Assets that are backed by on-chain deposits - -## Checking Off-Chain Balances - -To monitor your channel funds, you need to retrieve the current off-chain balances from the ClearNode. - -## Understanding the Ledger Balances Request - -The `get_ledger_balances` request is used to retrieve the current off-chain balances for a specific participant from the ClearNode: - -- **Request params**: `[{ participant: "0xAddress" }]` where `0xAddress` is the participant's address -- **Response**: Array containing the balances for different assets held by the participant - -The response contains a list of assets and their amounts for the specified participant. The balances are represented as strings with decimal precision, making it easier to display them directly without additional conversion. - -```javascript -// Example response format for get_ledger_balances -{ - "res": [1, "get_ledger_balances", [[ // The nested array contains balance data - { - "asset": "usdc", // Asset identifier - "amount": "100.0" // Amount as a string with decimal precision - }, - { - "asset": "eth", - "amount": "0.5" - } - ]], 1619123456789], // Timestamp - "sig": ["0xabcd1234..."] -} -``` - -To retrieve these balances, use the `get_ledger_balances` request with the ClearNode: - - - - -```javascript -import { createGetLedgerBalancesMessage, parseRPCMessage, RPCMethod } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// Your message signer function (same as in auth flow) -const messageSigner = async (payload) => { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - return signature; -}; - -// Function to get ledger balances -async function getLedgerBalances(ws, participant) { - return new Promise((resolve, reject) => { - // Create a unique handler for this specific request - const handleMessage = (event) => { - const message = parseRPCMessage(event.data); - - // Check if this is a response to our get_ledger_balances request - if (message.method === RPCMethod.GetLedgerBalances) { - // Remove the message handler to avoid memory leaks - ws.removeEventListener('message', handleMessage); - - // Resolve with the balances data - resolve(message.params); - } - }; - - // Add the message handler - ws.addEventListener('message', handleMessage); - - // Create and send the ledger balances request - createGetLedgerBalancesMessage(messageSigner, participant) - .then(message => { - ws.send(message); - }) - .catch(error => { - // Remove the message handler on error - ws.removeEventListener('message', handleMessage); - reject(error); - }); - - // Set a timeout to prevent hanging indefinitely - setTimeout(() => { - ws.removeEventListener('message', handleMessage); - reject(new Error('Timeout waiting for ledger balances')); - }, 10000); // 10 second timeout - }); -} - -// Usage example -const participantAddress = '0x1234567890abcdef1234567890abcdef12345678'; - -try { - const balances = await getLedgerBalances(ws, participantAddress); - - console.log('Channel ledger balances:', balances); - // Example output: - // [ - // [ - // { "asset": "usdc", "amount": "100.0" }, - // { "asset": "eth", "amount": "0.5" } - // ] - // ] - - // Process your balances - if (balances.length > 0) { - // Display each asset balance - balances.forEach(balance => { - console.log(`${balance.asset.toUpperCase()} balance: ${balance.amount}`); - }); - - // Example: find a specific asset - const usdcBalance = balances.find(b => b.asset.toLowerCase() === 'usdc'); - if (usdcBalance) { - console.log(`USDC balance: ${usdcBalance.amount}`); - } - } else { - console.log('No balance data available'); - } -} catch (error) { - console.error('Failed to get ledger balances:', error); -} -``` - - - - -```javascript -import { ethers } from 'ethers'; -import { generateRequestId, getCurrentTimestamp, generateRequestId, parseRPCMessage, RPCMethod } from '@erc7824/nitrolite'; - -// Function to create a signed ledger balances request -async function createLedgerBalancesRequest(signer, participant) { - const requestId = generateRequestId(); - const method = RPCMethod.GetLedgerBalances; // Use the RPC method enum for clarity - const params = [{ participant }]; // Note: updated parameter name to 'participant' - const timestamp = getCurrentTimestamp(); - - // Create the request structure - const requestData = [requestId, method, params, timestamp]; - const request = { req: requestData }; - - // Sign the request - const message = JSON.stringify(request); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = signer.wallet.signingKey.sign(messageBytes); - - // Add signature to the request - request.sig = [signature]; - - return { stringified: JSON.stringify(request), requestId }; -} - -// Function to get ledger balances -async function getLedgerBalances(ws, participant, signer) { - return new Promise((resolve, reject) => { - createLedgerBalancesRequest(signer, participant) - .then(({ stringified, requestId }) => { - // Set up message handler - const handleMessage = (event) => { - try { - const message = parseRPCMessage(event.data); - - // Check if this is our response - if (message.requestId === requestId && - message.method === RPCMethod.GetLedgerBalances) { - - // Remove the listener - ws.removeEventListener('message', handleMessage); - - // Resolve with the balances data - resolve(message.params); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - // Add message listener - ws.addEventListener('message', handleMessage); - - // Send the request - ws.send(stringified); - - // Set timeout - setTimeout(() => { - ws.removeEventListener('message', handleMessage); - reject(new Error('Timeout waiting for ledger balances')); - }, 10000); - }) - .catch(reject); - }); -} - -// Usage example -const participantAddress = '0x1234567890abcdef1234567890abcdef12345678'; - -try { - const balances = await getLedgerBalances(ws, participantAddress, stateWallet); - - console.log('Channel ledger balances:', balances); - // Process and display balances - // ... - -} catch (error) { - console.error('Failed to get ledger balances:', error); -} -``` - - - - -## Checking Balances for a Participant - -To retrieve off-chain balances for a participant, use the `createGetLedgerBalancesMessage` helper function: - -```javascript -import { createGetLedgerBalancesMessage, parseRPCResponse, RPCMethod } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// Function to get ledger balances for a participant -async function getLedgerBalances(ws, participant, messageSigner) { - return new Promise((resolve, reject) => { - // Message handler for the response - const handleMessage = (event) => { - try { - const message = parseRPCResponse(event.data); - - // Check if this is a response to our get_ledger_balances request - if (message.method === RPCMethod.GetLedgerBalances) { - // Clean up by removing the event listener - ws.removeEventListener('message', handleMessage); - - // Resolve with the balance data - resolve(message.params); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - // Set up timeout to avoid hanging indefinitely - const timeoutId = setTimeout(() => { - ws.removeEventListener('message', handleMessage); - reject(new Error('Timeout waiting for ledger balances')); - }, 10000); // 10 second timeout - - // Add the message handler - ws.addEventListener('message', handleMessage); - - // Create and send the balance request - createGetLedgerBalancesMessage(messageSigner, participant) - .then(message => { - ws.send(message); - }) - .catch(error => { - clearTimeout(timeoutId); - ws.removeEventListener('message', handleMessage); - reject(error); - }); - }); -} - -// Example usage -const participantAddress = '0x1234567890abcdef1234567890abcdef12345678'; - -getLedgerBalances(ws, participantAddress, messageSigner) - .then(balances => { - console.log('Channel balances:', balances); - - // Process and display your balances - if (balances.length > 0) { - console.log('My balances:'); - balances.forEach(balance => { - console.log(`- ${balance.asset.toUpperCase()}: ${balance.amount}`); - }); - } else { - console.log('No balance data available'); - } - }) - .catch(error => { - console.error('Failed to get ledger balances:', error); - }); -``` - -## Processing Balance Data - -When you receive balance data from the ClearNode, it's helpful to format it for better readability: - -```javascript -// Simple function to format your balance data for display -function formatMyBalances(balances) { - // Return formatted balance information - return balances.map(balance => ({ - asset: balance.asset.toUpperCase(), - amount: balance.amount, - // You can add additional formatting here if needed - displayAmount: `${balance.amount} ${balance.asset.toUpperCase()}` - })); -} - -// Example usage -const myFormattedBalances = formatMyBalances(balancesFromClearNode); - -if (myFormattedBalances && myFormattedBalances.length > 0) { - console.log('My balances:'); - myFormattedBalances.forEach(balance => { - console.log(`- ${balance.displayAmount}`); - }); -} else { - console.log('No balance data available'); -} -``` - -## Best Practices for Balance Checking - -When working with off-chain balances, follow these best practices: - -### Regular Balance Polling - -Set up a regular interval to check balances, especially in active applications: - -```javascript -// Simple balance monitoring function -function startBalanceMonitoring(ws, participantAddress, messageSigner, intervalMs = 30000) { - // Check immediately on start - getLedgerBalances(ws, participantAddress, messageSigner) - .then(displayBalances) - .catch(err => console.error('Initial balance check failed:', err)); - - // Set up interval for regular checks - const intervalId = setInterval(() => { - getLedgerBalances(ws, participantAddress, messageSigner) - .then(displayBalances) - .catch(err => console.error('Balance check failed:', err)); - }, intervalMs); // Check every 30 seconds by default - - // Return function to stop monitoring - return () => clearInterval(intervalId); -} - -// Simple display function -function displayBalances(balances) { - console.log(`Balance update at ${new Date().toLocaleTimeString()}:`); - - // Format and display your balances - if (balances.length > 0) { - console.log('My balances:'); - balances.forEach(balance => { - console.log(`- ${balance.asset.toUpperCase()}: ${balance.amount}`); - }); - } else { - console.log('No balance data available'); - } -} -``` - -## Common Errors and Troubleshooting - -When retrieving off-chain balances, you might encounter these common issues: - -| Error Type | Description | Solution | -|------------|-------------|----------| -| **Authentication errors** | WebSocket connection loses authentication | Re-authenticate before requesting balances again | -| **Channel not found** | The channel ID is invalid or the channel has been closed | Verify the channel ID and check if the channel is still active | -| **Connection issues** | WebSocket disconnects during a balance request | Implement reconnection logic with exponential backoff | -| **Timeout** | The ClearNode does not respond in a timely manner | Set appropriate timeouts and implement retry logic | - -## Next Steps - -Now that you understand how to monitor off-chain balances in your channels, you can: - -1. [Create an application session](application_session) to start transacting off-chain -2. Learn about [channel closing](close_session) when you're done with the channel \ No newline at end of file diff --git a/erc7824-docs/docs/quick_start/close_session.md b/erc7824-docs/docs/quick_start/close_session.md deleted file mode 100644 index d933fb152..000000000 --- a/erc7824-docs/docs/quick_start/close_session.md +++ /dev/null @@ -1,1197 +0,0 @@ ---- -sidebar_position: 8 -title: Close Application Session -description: Properly close application sessions and finalize fund allocations using NitroliteRPC. -keywords: [erc7824, nitrolite, close session, finalize, state channels] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Close Application Session - -Once you've completed your interactions with an application session, it's important to properly close it. This finalizes the fund allocations and ensures that all participants agree on the final state. - -## Why Properly Closing Sessions Matters - -Closing an application session correctly is important because it: - -- Finalizes fund allocations between participants -- Updates on-chain balances (if applicable) -- Frees up resources on the ClearNode -- Ensures the proper completion of all operations -- Prevents potential disputes - -```mermaid -graph TD - A[Active Session] --> B[Define Final Allocations] - B --> C[Sign Close Message] - C --> D[Send to ClearNode] - D --> E[Session Closed] -``` - -## Closing an Application Session - -To close an application session, you'll use the `createCloseAppSessionMessage` helper from NitroliteRPC. Here's how to do it: - - - - -```javascript -import { useCallback } from 'react'; -import { createCloseAppSessionMessage, parseRPCResponse, MessageSigner, CloseAppSessionRPCParams } from '@erc7824/nitrolite'; - -/** - * Hook for closing an application session - */ -function useCloseApplicationSession() { - const closeApplicationSession = useCallback( - async ( - signer: MessageSigner, - sendRequest: (message: string) => Promise, - appId: string, - participantA: Address, - participantB: Address, - amount: string - ) => { - try { - if (!appId) { - throw new Error('Application ID is required to close the session.'); - } - - // Create allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: '0', - }, - { - participant: participantB, - asset: 'usdc', - amount: amount, - }, - ]; - - // Create the close request - const closeRequest = { - app_session_id: appId, - allocations: allocations, - }; - - // Create the signed message - const signedMessage = await createCloseAppSessionMessage( - signer, - [closeRequest] - ); - - // Send the request and wait for response - const response = await sendRequest(signedMessage); - - // Check for success - if (response.app_session_id) { - // Clean up local storage - localStorage.removeItem('app_session_id'); - return { - success: true, - app_id: response.app_session_id, - status: response.status || "closed", - response - }; - } else { - return { success: true, response }; - } - } catch (error) { - console.error('Error closing application session:', error); - return { - success: false, - error: error instanceof Error - ? error.message - : 'Unknown error during close session', - }; - } - }, - [] - ); - - return { closeApplicationSession }; -} - -// Usage example -function MyComponent() { - const { closeApplicationSession } = useCloseApplicationSession(); - - const handleCloseSession = async () => { - // Define your WebSocket send wrapper - const sendRequest = async (payload) => { - return new Promise((resolve, reject) => { - // Assuming ws is your WebSocket connection - const handleMessage = (event) => { - try { - const message = parseRPCResponse(event.data); - if (message.method === RPCMethod.CloseAppSession) { - ws.removeEventListener('message', handleMessage); - resolve(message.params); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - ws.addEventListener('message', handleMessage); - ws.send(payload); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeEventListener('message', handleMessage); - reject(new Error('Close app session timeout')); - }, 10000); - }); - }; - - // Get the app session ID from where it was stored - const appId = localStorage.getItem('app_session_id'); - - // Define participant addresses - const participantA = '0xYourAddress'; - const participantB = '0xOtherAddress'; - - // Define amount - const amount = '1000000'; // Amount to allocate to participantB - - const result = await closeApplicationSession( - walletSigner, // Your signer object - sendRequest, // Function to send the request - appId, // Application session ID - participantA, // First participant address - participantB, // Second participant address - amount // Amount to allocate to participant B - ); - - if (result.success) { - console.log('Application session closed successfully'); - } else { - console.error(`Failed to close application session: ${result.error}`); - } - }; - - return ( - - ); -} -``` - - - - -```typescript -// app-session.service.ts -import { Injectable } from '@angular/core'; -import { createCloseAppSessionMessage } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; -import { BehaviorSubject, Observable, from } from 'rxjs'; -import { tap, catchError } from 'rxjs/operators'; - -@Injectable({ - providedIn: 'root' -}) -export class AppSessionService { - private webSocket: WebSocket | null = null; - private appIdSubject = new BehaviorSubject(null); - - public appId$ = this.appIdSubject.asObservable(); - - constructor() { - // Retrieve app ID from storage if available - const storedAppId = localStorage.getItem('app_session_id'); - if (storedAppId) { - this.appIdSubject.next(storedAppId); - } - } - - public setWebSocket(ws: WebSocket): void { - this.webSocket = ws; - } - - // This method closes an application session - public closeApplicationSession( - signer: any, - appId: string, - finalAllocations: number[] - ): Observable { - if (!this.webSocket) { - throw new Error('WebSocket connection is not established'); - } - - if (!appId) { - throw new Error('Application ID is required'); - } - - return from(this.closeAppSessionAsync( - signer, - appId, - participantA, - participantB, - amount - )).pipe( - tap(result => { - if (result.success) { - // Remove app ID from storage - localStorage.removeItem('app_session_id'); - this.appIdSubject.next(null); - } - }), - catchError(error => { - console.error('Error closing application session:', error); - throw error; - }) - ); - } - - private async closeAppSessionAsync( - signer: any, - appId: string, - participantA: string, - participantB: string, - amount: string - ): Promise { - try { - // Create allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: '0', - }, - { - participant: participantB, - asset: 'usdc', - amount: amount, - }, - ]; - - // Create close request - const closeRequest = { - app_session_id: appId, - allocations: allocations, - }; - - - // Create message signer function - const messageSigner = async (payload: any) => { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const signature = await signer.signMessage(messageBytes); - return signature; - }; - - // Create the signed message - const signedMessage = await createCloseAppSessionMessage( - messageSigner, - [closeRequest] - ); - - // Send the message and wait for response - return await this.sendRequest(signedMessage); - } catch (error) { - console.error('Error in closeAppSessionAsync:', error); - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }; - } - } - - private sendRequest(payload: string): Promise { - return new Promise((resolve, reject) => { - if (!this.webSocket) { - reject(new Error('WebSocket not connected')); - return; - } - - const handleMessage = (event: MessageEvent) => { - try { - const message = JSON.parse(event.data); - if (message.res && message.res[1] === 'close_app_session') { - this.webSocket?.removeEventListener('message', handleMessage); - resolve({ - success: true, - app_id: message.res[2]?.[0]?.app_session_id || null, - status: message.res[2]?.[0]?.status || "closed", - response: message.res[2] - }); - } - - if (message.err) { - this.webSocket?.removeEventListener('message', handleMessage); - reject(new Error(`Error: ${message.err[1]} - ${message.err[2]}`)); - } - } catch (error) { - console.error('Error parsing message:', error); - } - }; - - this.webSocket.addEventListener('message', handleMessage); - this.webSocket.send(payload); - - // Set timeout to prevent hanging - setTimeout(() => { - this.webSocket?.removeEventListener('message', handleMessage); - reject(new Error('Close session timeout')); - }, 10000); - }); - } -} - -// app-session-close.component.ts -import { Component, OnInit } from '@angular/core'; -import { AppSessionService } from './app-session.service'; - -@Component({ - selector: 'app-session-closer', - template: ` -
-

Close Application Session

-
- Current Application Session ID: {{ appId }} - -
-
- - -
-
- - -
-
- - -
- -
- No active application session found. -
- -
- {{ error }} -
- -
- Application session closed successfully! -
-
- `, - styles: [` - .app-session-container { - margin: 20px; - padding: 15px; - border: 1px solid #eee; - border-radius: 5px; - } - .allocation-controls { - margin: 15px 0; - } - .allocation-item { - margin-bottom: 10px; - } - .error-message { - color: red; - margin-top: 10px; - } - .success-message { - color: green; - margin-top: 10px; - } - .info-message { - color: blue; - margin-top: 10px; - } - `] -}) -export class AppSessionCloseComponent implements OnInit { - appId: string | null = null; - isClosing = false; - error: string | null = null; - success = false; - - // Default allocations (e.g., 800000 / 200000 split for 1 USDC) - allocation1 = 800000; - allocation2 = 200000; - - constructor(private appSessionService: AppSessionService) {} - - ngOnInit(): void { - // Subscribe to app ID changes - this.appSessionService.appId$.subscribe(id => { - this.appId = id; - this.success = false; - }); - } - - closeAppSession(): void { - if (!this.appId) { - this.error = 'No active application session'; - return; - } - - this.isClosing = true; - this.error = null; - this.success = false; - - // Define participants - const participantA = '0xYourAddress'; - const participantB = '0xOtherAddress'; - - // Create allocations with asset type - const amount = this.allocation2.toString(); - - // Assuming you have access to a signer (e.g., from MetaMask) - const signer = window.ethereum && new ethers.providers.Web3Provider(window.ethereum).getSigner(); - - if (!signer) { - this.error = 'No wallet connected'; - this.isClosing = false; - return; - } - - this.appSessionService.closeApplicationSession( - signer, - this.appId, - participantA, - participantB, - amount - ).subscribe({ - next: (result) => { - console.log('App session closed:', result); - this.success = true; - this.isClosing = false; - }, - error: (err) => { - this.error = `Failed to close application session: ${err.message}`; - this.isClosing = false; - } - }); - } -} -``` - -
- - -```javascript - - - - - - -``` - - - - -```javascript -import { createCloseAppSessionMessage } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -/** - * Close an app session - * @param {string} appId - The app session ID to close - * @param {string} participantA - First participant's address - * @param {string} participantB - Second participant's address - * @param {string} amount - Amount to allocate to participant B - * @param {WebSocket} ws - WebSocket connection to the ClearNode - * @param {object} wallet - Ethers wallet for signing - * @returns {Promise} Success status - */ -async function closeAppSession(appId, participantA, participantB, amount, ws, wallet) { - try { - console.log(`Closing app session ${appId} with amount ${amount}`); - - if (!appId) { - throw new Error('App session ID is required to close the session'); - } - - // Message signer function - const messageSigner = async (payload) => { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = wallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }; - - // Create allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: '0', - }, - { - participant: participantB, - asset: 'usdc', - amount: amount, - }, - ]; - - // Create the close request - const closeRequest = { - app_session_id: appId, - allocations: allocations, - }; - - // Create the signed message - const signedMessage = await createCloseAppSessionMessage( - messageSigner, - [closeRequest] - ); - - // Send the message and wait for response - return new Promise((resolve, reject) => { - // Create a one-time message handler for the close session response - const handleCloseSessionResponse = (data) => { - try { - const rawData = typeof data === 'string' ? data : data.toString(); - const message = JSON.parse(rawData); - - console.log('Received close session response:', message); - - // Check if this is a close session response - if (message.res && - (message.res[1] === 'close_app_session' || - message.res[1] === 'app_session_closed')) { - // Remove the listener once we get the response - ws.removeListener('message', handleCloseSessionResponse); - - // Extract app ID and status from response - const appId = message.res[2]?.[0]?.app_id; - const status = message.res[2]?.[0]?.status || "closed"; - - resolve({ - success: true, - app_session_id: appId, - status: status - }); - } - - // Check for error responses - if (message.err) { - ws.removeListener('message', handleCloseSessionResponse); - reject(new Error(`Error ${message.err[1]}: ${message.err[2]}`)); - } - } catch (error) { - console.error('Error handling close session response:', error); - } - }; - - // Add the message handler - ws.on('message', handleCloseSessionResponse); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeListener('message', handleCloseSessionResponse); - reject(new Error('Close session timeout')); - }, 10000); - - // Send the signed message - ws.send(signedMessage); - }); - } catch (error) { - console.error(`Error closing app session ${appId}:`, error); - throw error; - } -} - -// Usage example -const appId = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; -const participantA = '0x1234...'; // Your address -const participantB = '0x5678...'; // Other participant's address -const amount = '200000'; // 0.2 USDC to participant B - -// Assuming you have a WebSocket connection and wallet initialized -closeAppSession(appId, participantA, participantB, amount, ws, wallet) - .then(result => { - if (result.success) { - console.log(`Application session ${result.app_id} closed successfully with status: ${result.status}`); - } - }) - .catch(error => { - console.error('Failed to close application session:', error); - }); -``` - - - - -```javascript -/** - * Close an app session for a game room - * @param {string} roomId - Room ID - * @param {Array} allocations - Final allocations [player1, player2, server] - * @param {WebSocket} ws - WebSocket connection to the ClearNode - * @param {object} wallet - Ethers wallet for signing - * @returns {Promise} Success status - */ -export async function closeAppSession(roomId, participantA, participantB, participantServer, amount, ws, wallet) { - try { - // Get the app session for this room - const appSession = roomAppSessions.get(roomId); - if (!appSession) { - console.warn(`No app session found for room ${roomId}`); - return false; - } - - // Make sure appSessionId exists - const appSessionId = appSession.appSessionId; - if (!appSessionId) { - console.error(`No appSessionId found in app session for room ${roomId}`); - return false; - } - - console.log(`Closing app session ${appSessionId} for room ${roomId}`); - - // Message signer function - const messageSigner = async (payload) => { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = wallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }; - - // Create allocations with asset type - const allocations = [ - { - participant: participantA, - asset: 'usdc', - amount: '0', - }, - { - participant: participantB, - asset: 'usdc', - amount: amount, - }, - { - participant: participantServer, - asset: 'usdc', - amount: '0', - }, - ]; - - // Create close request - const closeRequest = { - app_session_id: appSessionId, - allocations: allocations, - }; - - // Create the signed message - const signedMessage = await createCloseAppSessionMessage( - messageSigner, - [closeRequest] - ); - - // Send the message and wait for response - return new Promise((resolve, reject) => { - // Create a one-time message handler for the close session response - const handleCloseSessionResponse = (data) => { - try { - const rawData = typeof data === 'string' ? data : data.toString(); - const message = JSON.parse(rawData); - - console.log('Received close session response:', message); - - // Check if this is a close session response - if (message.res && - (message.res[1] === 'close_app_session' || - message.res[1] === 'app_session_closed')) { - // Remove the listener once we get the response - ws.removeListener('message', handleCloseSessionResponse); - - // Extract app ID and status from response - const appId = message.res[2]?.[0]?.app_id; - const status = message.res[2]?.[0]?.status || "closed"; - - // Remove the app session - roomAppSessions.delete(roomId); - - resolve({ - success: true, - app_session_id: appId, - status: status, - roomId: roomId - }); - } - - // Check for error responses - if (message.err) { - ws.removeListener('message', handleCloseSessionResponse); - reject(new Error(`Error ${message.err[1]}: ${message.err[2]}`)); - } - } catch (error) { - console.error('Error handling close session response:', error); - } - }; - - // Add the message handler - ws.on('message', handleCloseSessionResponse); - - // Set timeout to prevent hanging - setTimeout(() => { - ws.removeListener('message', handleCloseSessionResponse); - reject(new Error('Close session timeout')); - }, 10000); - - // Send the signed message - ws.send(signedMessage); - }); - } catch (error) { - console.error(`Error closing app session for room ${roomId}:`, error); - return false; - } -} -``` - - -
- -## Close Session Flow - -The following sequence diagram shows the interaction between your application and the ClearNode when closing a session: - -```mermaid -sequenceDiagram - participant App as Your Application - participant CN as ClearNode - - App->>CN: Send close_app_session request - CN->>App: Return success response - App->>App: Clean up local storage -``` - -## Understanding Final Allocations - -When closing an application session, you must specify the final allocations of funds between participants: - -- **Final Allocations**: An array of allocation objects for each participant -- **Participant**: The address of the participant receiving funds -- **Asset**: The asset type (e.g., "usdc", "eth") -- **Amount**: The amount as a string (e.g., "1000000" or "0.5") - -Examples: - -```javascript -// Initial allocations when creating the session: -// [ -// { participant: participantA, asset: "usdc", amount: "1000000" }, -// { participant: participantB, asset: "usdc", amount: "0" } -// ] - -// Possible final allocations when closing: -const allocations = [ - { participant: participantA, asset: "usdc", amount: "1000000" }, - { participant: participantB, asset: "usdc", amount: "0" } -]; // No change - all funds to participant A - -// OR -const allocations = [ - { participant: participantA, asset: "usdc", amount: "0" }, - { participant: participantB, asset: "usdc", amount: "1000000" } -]; // All funds to participant B - -// OR -const allocations = [ - { participant: participantA, asset: "usdc", amount: "700000" }, - { participant: participantB, asset: "usdc", amount: "300000" } -]; // Split 70/30 -``` - -## Understanding the Close Session Response - -When you close an application session, the ClearNode responds with information about the closed session: - -```javascript -// Example response -{ - "res": [ - 3, // Request ID - "close_app_session", // Method name - [ - { - "app_session_id": "0x0ac588b2924edbbbe34bb4c51d089771bd7bd7018136c8c4317624112a8c9f79", // Session ID - "status": "closed" - } - ], - 1631234567890 // Timestamp - ], - "sig": ["0xSignature"] -} -``` - -## Response Components - -When closing an application session, the ClearNode responds with: - -| Component | Description | Example | -|-----------|-------------|---------| -| **app_session_id** | Identifier of the application session | `"0x0ac588b2924edbbbe34bb4c51d089771bd7bd7018136c8c4317624112a8c9f79"` | -| **status** | Final state of the application session | `"closed"` | - -## Common Scenarios for Closing Sessions - -Here are some common scenarios where you would close an application session: - -| Scenario | Description | Typical Allocation Pattern | -|----------|-------------|---------------------------| -| **Payment Completed** | User completes payment for a service | All funds to the service provider | -| **Game Completed** | End of a game with a winner | Winner gets the pot | -| **Partial Service** | Service partially delivered | Split based on completion percentage | -| **Cancellation** | Service cancelled before completion | Funds returned to original funder | -| **Dispute Resolution** | Resolution after a dispute | Split according to dispute resolution | - - -## Best Practices - -When closing application sessions, follow these best practices: - -1. **Verify all transactions are complete** before closing -2. **Include appropriate timeouts** to prevent hanging operations -3. **Clean up event listeners** to prevent memory leaks -4. **Implement retry logic** for failure recovery -5. **Store transaction records** for audit purposes -6. **Validate final allocations** match expected state - -## Common Errors and Troubleshooting - -When closing application sessions, you might encounter these issues: - -| Error Type | Description | Solution | -|------------|-------------|----------| -| **Authentication errors** | WebSocket connection loses authentication | Re-authenticate before closing the session | -| **Session not found** | The app_session_id is invalid or the session was already closed | Verify the app_session_id is correct and the session is still active | -| **Allocation mismatch** | The total in final allocations doesn't match initial funding | Ensure allocations sum to the correct total amount | -| **Connection issues** | WebSocket disconnects during a close request | Implement reconnection logic with exponential backoff | -| **Timeout** | The ClearNode does not respond in a timely manner | Set appropriate timeouts and implement retry logic | - -## Next Steps - -After closing an application session, you can: - -1. [Check your channel balances](balances) to verify the finalized allocations -2. [Create a new application session](application_session) if you need to continue with different parameters - -For more advanced scenarios, see our detailed documentation on state channel lifecycle management. \ No newline at end of file diff --git a/erc7824-docs/docs/quick_start/connect_to_the_clearnode.md b/erc7824-docs/docs/quick_start/connect_to_the_clearnode.md deleted file mode 100644 index dbe8a787e..000000000 --- a/erc7824-docs/docs/quick_start/connect_to_the_clearnode.md +++ /dev/null @@ -1,1791 +0,0 @@ ---- -sidebar_position: 5 -title: Connect to the ClearNode -description: Establish connection with ClearNode for reliable off-chain transaction processing and verification. -keywords: [erc7824, nitrolite, clearnode, off-chain, validation, messaging, nitroliterpc, websocket] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Connect to the ClearNode - -A ClearNode is a specialized service that facilitates off-chain communication, message relay, and state validation in the Nitrolite ecosystem. This guide explains how to establish and manage connections to a ClearNode using the NitroliteRPC protocol. - -## What is a ClearNode? - -A **[ClearNode](https://github.com/erc7824/clearnode)** is an implementation of a message broker for the Clearnet protocol. It serves as a critical infrastructure component in the Nitrolite ecosystem, providing several important functions in the state channel network: - -- **Multi-Chain Support**: Connect to multiple EVM blockchains (Polygon, Celo, Base) -- **Off-Chain Payments**: Efficient payment channels for high-throughput transactions -- **Virtual Applications**: Create multi-participant applications -- **Quorum-Based Signatures**: Support for multi-signature schemes with weight-based quorums - -## Understanding NitroliteRPC - -NitroliteRPC is a utility in our SDK that standardizes message creation for communication with ClearNodes. It's not a full protocol implementation but rather a set of helper functions that ensure your application constructs properly formatted messages for ClearNode interaction. - -Key functions of NitroliteRPC include: - -- **Message Construction**: Creates properly formatted request messages -- **Signature Management**: Handles the cryptographic signing of messages -- **Standard Format Enforcement**: Ensures all messages follow the required format for ClearNode compatibility -- **Authentication Flow Helpers**: Simplifies the authentication process - -Under the hood, NitroliteRPC provides functions that generate message objects with the correct structure, timestamps, and signature formatting so you don't have to build these messages manually when communicating with ClearNodes. - -## Connecting to a ClearNode - -After initializing your client and creating a channel, you need to establish a WebSocket connection to a ClearNode. It's important to understand that the Nitrolite SDK doesn't provide its own transport layer - you'll need to implement the WebSocket connection yourself using your preferred library. - - - - -```javascript -// Import your preferred WebSocket library -import WebSocket from 'ws'; // Node.js -// or use the browser's built-in WebSocket - -// Create a WebSocket connection to the ClearNode -const ws = new WebSocket('wss://clearnode.example.com'); - -// Set up basic event handlers -ws.onopen = () => { - console.log('WebSocket connection established'); - // Connection is open, can now proceed with authentication -}; - -ws.onmessage = (event) => { - const message = JSON.parse(event.data); - console.log('Received message:', message); - // Process incoming messages -}; - -ws.onerror = (error) => { - console.error('WebSocket error:', error); -}; - -ws.onclose = (event) => { - console.log(`WebSocket closed: ${event.code} ${event.reason}`); -}; -``` - - - - -```javascript -class ClearNodeConnection { - constructor(url) { - this.url = url; - this.ws = null; - this.isConnected = false; - this.reconnectAttempts = 0; - this.maxReconnectAttempts = 5; - this.reconnectInterval = 3000; // ms - this.messageHandlers = new Map(); - } - - // Register message handlers - onMessage(type, handler) { - this.messageHandlers.set(type, handler); - } - - connect() { - this.ws = new WebSocket(this.url); - - this.ws.onopen = this.handleOpen.bind(this); - this.ws.onmessage = this.handleMessage.bind(this); - this.ws.onerror = this.handleError.bind(this); - this.ws.onclose = this.handleClose.bind(this); - } - - handleOpen() { - console.log(`Connected to ClearNode at ${this.url}`); - this.isConnected = true; - this.reconnectAttempts = 0; - - // Emit connected event - this.emit('connected'); - } - - handleMessage(event) { - try { - const message = JSON.parse(event.data); - - // Determine message type (auth_challenge, auth_success, etc.) - const messageType = message.res ? message.res[1] : 'unknown'; - - // Emit specific message event - this.emit('message', message); - - // Call specific handler if registered - if (this.messageHandlers.has(messageType)) { - this.messageHandlers.get(messageType)(message); - } - } catch (error) { - console.error('Error handling message:', error); - } - } - - handleError(error) { - console.error('WebSocket error:', error); - this.emit('error', error); - } - - handleClose(event) { - this.isConnected = false; - console.log(`WebSocket closed: ${event.code} ${event.reason}`); - - // Emit disconnected event - this.emit('disconnected', event); - - // Attempt to reconnect - this.attemptReconnect(); - } - - attemptReconnect() { - if (this.reconnectAttempts >= this.maxReconnectAttempts) { - console.error('Maximum reconnection attempts reached'); - return; - } - - this.reconnectAttempts++; - const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1); - - console.log(`Attempting to reconnect in ${delay}ms (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); - - setTimeout(() => { - console.log('Reconnecting...'); - this.connect(); - }, delay); - } - - send(message) { - if (!this.isConnected) { - console.error('Cannot send message: not connected'); - return false; - } - - try { - this.ws.send(typeof message === 'string' ? message : JSON.stringify(message)); - return true; - } catch (error) { - console.error('Error sending message:', error); - return false; - } - } - - disconnect() { - if (this.ws) { - this.ws.close(1000, 'User initiated disconnect'); - } - } - - // Simple event system - #events = {}; - - on(event, callback) { - if (!this.#events[event]) this.#events[event] = []; - this.#events[event].push(callback); - return this; - } - - off(event, callback) { - if (!this.#events[event]) return this; - if (!callback) { - delete this.#events[event]; - return this; - } - this.#events[event] = this.#events[event].filter(cb => cb !== callback); - return this; - } - - emit(event, ...args) { - if (!this.#events[event]) return false; - this.#events[event].forEach(callback => callback(...args)); - return true; - } -} - -// Usage -const clearNodeConnection = new ClearNodeConnection('wss://clearnode.example.com'); -clearNodeConnection.connect(); - -// Register event handlers -clearNodeConnection.on('connected', () => { - console.log('Connection established, ready to authenticate'); -}); - -// Later, when you're done -clearNodeConnection.disconnect(); -``` - - - - -## Authentication Flow - -When connecting to a ClearNode, you need to follow a specific authentication flow using the NitroliteRPC utility to create properly formatted and signed messages: - -1. **Initial Connection**: The client establishes a WebSocket connection to the ClearNode's URL -2. **Auth Request**: On the first connection client sends an `auth_request` message with its identity information -3. **Challenge**: The ClearNode responds with an `auth_challenge` containing a random nonce -4. **Signature Verification**: The client signs the challenge along with session key and allowances using EIP712 signature and sends an `auth_verify` message -5. **Auth Result**: The ClearNode verifies the signature and responds with `auth_success` or `auth_failure` -6. **Reconnection**: On success ClearNode will return the JWT Token, which can be used for subsequent reconnections without needing to re-authenticate. - -This flow ensures that only authorized participants with valid signing keys can connect to the ClearNode and participate in channel operations. - -```mermaid -sequenceDiagram - participant Client as Your Application - participant CN as ClearNode - - Client->>CN: WebSocket Connection Request - CN->>Client: Connection Established - - Client->>Client: Create auth_request - Client->>CN: Send auth_request - - CN->>CN: Generate random challenge nonce - CN->>Client: Send auth_challenge with nonce - - Client->>Client: Sign challenge using EIP712 - Client->>CN: Send auth_verify with signature - - CN->>CN: Verify signature against address - - alt Authentication Success - CN->>Client: Send auth_success - Note over Client,CN: Client is now authenticated - Client->>CN: Can now send channel operations - else Authentication Failure - CN->>Client: Send auth_failure - Note over Client,CN: Connection remains but unauthorized - end -``` - - - - -```javascript -import { - createAuthRequestMessage, - createAuthVerifyMessage, - createEIP712AuthMessageSigner, - parseRPCResponse, - RPCMethod, -} from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// Create and send auth_request -const authRequestMsg = await createAuthRequestMessage({ - address: '0xYourWalletAddress', - session_key: '0xYourSignerAddress', - application: 'YourAppDomain', - expires_at: (Math.floor(Date.now() / 1000) + 3600).toString(), // 1 hour expiration (as string) - scope: 'console', - allowances: [], -}); - -// After WebSocket connection is established -ws.onopen = async () => { - console.log('WebSocket connection established'); - - ws.send(authRequestMsg); -}; - -// Handle incoming messages -ws.onmessage = async (event) => { - try { - const message = parseRPCResponse(event.data); - - // Handle auth_challenge response - switch (message.method) { - case RPCMethod.AuthChallenge: - console.log('Received auth challenge'); - - // Create EIP-712 message signer function - const eip712MessageSigner = createEIP712AuthMessageSigner( - walletClient, // Your wallet client instance - { - // EIP-712 message structure, data should match auth_request - scope: authRequestMsg.scope, - application: authRequestMsg.application, - participant: authRequestMsg.participant, - expires_at: authRequestMsg.expires_at, - allowances: authRequestMsg.allowances, - }, - { - // Domain for EIP-712 signing - name: 'Your Domain', - }, - ) - - // Create and send auth_verify with signed challenge - const authVerifyMsg = await createAuthVerifyMessage( - eip712MessageSigner, // Our custom eip712 signer function - message, - ); - - ws.send(authVerifyMsg); - break; - // Handle auth_success or auth_failure - case RPCMethod.AuthVerify: - if (!message.params.success) { - console.log('Authentication failed'); - return; - } - console.log('Authentication successful'); - // Now you can start using the channel - - window.localStorage.setItem('clearnode_jwt', message.params.jwtToken); // Store JWT token for future use - break; - case RPCMethod.Error: { - console.error('Authentication failed:', message.params.error); - } - } - } catch (error) { - console.error('Error handling message:', error); - } -}; -``` - - - - -```javascript -import { - createAuthRequestMessage, - createAuthVerifyMessageFromChallenge, - createGetLedgerBalancesMessage, - createGetConfigMessage, - createEIP712AuthMessageSigner, - parseRPCResponse, - RPCMethod, -} from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// After connection is established, send auth request -ws.onopen = async () => { - const authRequestMsg = await createAuthRequestMessage({ - address: '0xYourWalletAddress', - session_key: '0xYourSignerAddress', - application: 'YourAppDomain', - expires_at: (Math.floor(Date.now() / 1000) + 3600).toString(), // 1 hour expiration (as string) - scope: 'console', - allowances: [], - }); - ws.send(authRequestMsg); -}; - -// If you want to manually extract and handle the challenge -ws.onmessage = async (event) => { - try { - const message = parseRPCResponse(event.data); - - if (message.method === RPCMethod.AuthChallenge) { - // Extract the challenge manually from the response - if ( - message.params.challengeMessage - ) { - const challenge = message.params.challengeMessage; - - // Create EIP-712 message signer function - const eip712MessageSigner = createEIP712AuthMessageSigner( - walletClient, // Your wallet client instance - { - // EIP-712 message structure, data should match auth_request - scope: authRequestMsg.scope, - application: authRequestMsg.application, - participant: authRequestMsg.participant, - expires_at: authRequestMsg.expires_at, - allowances: authRequestMsg.allowances, - }, - { - // Domain for EIP-712 signing - name: 'Your Domain', - }, - ) - - // Create auth_verify with the explicitly provided challenge - const authVerifyMsg = await createAuthVerifyMessageFromChallenge( - eip712MessageSigner, - challenge - ); - - ws.send(authVerifyMsg); - } else { - console.error('Malformed challenge response'); - } - } - } catch (error) { - console.error('Error handling message:', error); - } -}; -``` - - - - -```javascript -import { createAuthVerifyMessageWithJWT, parseRPCResponse, RPCMethod } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; - -// After WebSocket connection is established -ws.onopen = async () => { - console.log('WebSocket connection established'); - - // Create and send auth_verify with JWT for reconnection - // Get the stored JWT token - const jwtToken = window.localStorage.getItem('clearnode_jwt'); - - const authRequestMsg = await createAuthVerifyMessageWithJWT( - jwtToken, // JWT token for reconnection - ); - - ws.send(authRequestMsg); -}; - -// Handle incoming messages -ws.onmessage = async (event) => { - try { - const message = parseRPCResponse(event.data); - - // Handle auth_success or auth_failure - switch (message.method) { - case RPCMethod.AuthVerify: - if (message.params.success) { - console.log('Authentication successful'); - // Now you can start using the channel - } - break; - case RPCMethod.Error: - console.error('Authentication failed:', message.params.error); - break; - } - } catch (error) { - console.error('Error handling message:', error); - } -}; -``` - - - - -### EIP-712 Signature - -In the authentication process, the client must sign messages using EIP-712 structured data signatures. This ensures that the messages are tamper-proof and verifiable by the ClearNode. - -The format of the EIP-712 message is as follows: - -```javascript -{ - "types": { - "EIP712Domain": [ - { "name": "name", "type": "string" } - ], - "Policy": [ - { "name": "challenge", "type": "string" }, - { "name": "scope", "type": "string" }, - { "name": "wallet", "type": "address" }, - { "name": "session_key", "type": "address" }, - { "name": "expires_at", "type": "uint64" }, - { "name": "allowances", "type": "Allowance[]" } - ], - "Allowance": [ - { "name": "asset", "type": "string" }, - { "name": "amount", "type": "string" } - ] - }, - // Domain and primary type - domain: { - name: 'Your App Domain' - }, - primaryType: 'Policy', - message: { - challenge: 'RandomChallengeString', - scope: 'console', - wallet: '0xYourWalletAddress', - session_key: '0xYourSignerAddress', - expires_at: 1762417301, - allowances: [] - } -} -``` - -### Message Signer - -In methods that require signing messages, that are not part of the authentication flow, you should use a custom message signer function `MessageSigner`. This function takes the payload and returns a signed message that can be sent to the ClearNode using ECDSA signature. - -There are also, several things to consider: this method SHOULD sign plain JSON payloads and NOT [ERC-191](https://eips.ethereum.org/EIPS/eip-191) data, because it allows signatures to be compatible with non-EVM chains. Since most of the libraries, like `ethers` or `viem`, use EIP-191 by default, you will need to overwrite the default behavior to sign plain JSON payloads. -The other thing to consider is that providing an EOA private key directly in the code is not recommended for production applications. Instead, we are recommending to generate session keys -- temporary keys that are used for signing messages during the session. This way, you can avoid exposing your main wallet's private key and reduce the risk of compromising your funds. - -The simplest implementation of a message signer function looks like this: - -> **Warning** -> For this example use `ethers` library version `5.7.2`. The `ethers` library version `6.x` has breaking changes that are not allowed in this example. - -```javascript -import { MessageSigner, RequestData, ResponsePayload } from '@erc7824/nitrolite'; -import { ethers } from 'ethers'; -import { Hex } from 'viem'; - -const messageSigner = async (payload: RequestData | ResponsePayload): Promise => { - try { - const wallet = new ethers.Wallet('0xYourPrivateKey'); - - const messageBytes = ethers.utils.arrayify(ethers.utils.id(JSON.stringify(payload))); - - const flatSignature = await wallet._signingKey().signDigest(messageBytes); - - const signature = ethers.utils.joinSignature(flatSignature); - - return signature as Hex; - } catch (error) { - console.error('Error signing message:', error); - throw error; - } -} -``` - -## Getting Channel Information - -After authenticating with a ClearNode, you can request information about your channels. This is useful to verify your connection is working correctly and to retrieve channel data. - -```javascript -import { createGetChannelsMessage, parseRPCResponse, RPCMethod } from '@erc7824/nitrolite'; - -// Example of using the function after authentication is complete -ws.addEventListener('message', async (event) => { - const message = parseRPCResponse(event.data); - - // Check if this is a successful authentication message - if (message.method === RPCMethod.AuthVerify && message.params.success) { - console.log('Successfully authenticated, requesting channel information...'); - - // Request channel information using the built-in helper function - const getChannelsMsg = await createGetChannelsMessage( - messageSigner, // Provide message signer function from previous example - client.stateWalletSigner.getAddress() - ); - - ws.send(getChannelsMsg); - } - - // Handle get_channels response - if (message.method === RPCMethod.GetChannels) { - console.log('Received channels information:'); - const channelsList = message.params; - - if (channelsList && channelsList.length > 0) { - channelsList.forEach((channel, index) => { - console.log(`Channel ${index + 1}:`); - console.log(`- Channel ID: ${channel.channel_id}`); - console.log(`- Status: ${channel.status}`); - console.log(`- Participant: ${channel.participant}`); - console.log(`- Token: ${channel.token}`); - console.log(`- Amount: ${channel.amount}`); - console.log(`- Chain ID: ${channel.chain_id}`); - console.log(`- Adjudicator: ${channel.adjudicator}`); - console.log(`- Challenge: ${channel.challenge}`); - console.log(`- Nonce: ${channel.nonce}`); - console.log(`- Version: ${channel.version}`); - console.log(`- Created: ${channel.created_at}`); - console.log(`- Updated: ${channel.updated_at}`); - }); - } else { - console.log('No active channels found'); - } - } -}); -``` - -### Response Format - -The response to a `get_channels` request includes detailed information about each channel: - -```javascript -{ - "res": [1, "get_channels", [[ // Notice the nested array structure - { - "channel_id": "0xfedcba9876543210...", - "participant": "0x1234567890abcdef...", - "status": "open", // Can be "open", "closed", "settling", etc. - "token": "0xeeee567890abcdef...", // ERC20 token address - "amount": "100000", // Current channel balance - "chain_id": 137, // Chain ID (e.g., 137 for Polygon) - "adjudicator": "0xAdjudicatorContractAddress...", // Contract address - "challenge": 86400, // Challenge period in seconds - "nonce": 1, - "version": 2, - "created_at": "2023-05-01T12:00:00Z", - "updated_at": "2023-05-01T12:30:00Z" - } - ]], 1619123456789], - "sig": ["0xabcd1234..."] -} -``` - -## Framework-Specific Integration - -Here are examples of integrating ClearNode WebSocket connections with various frameworks. Since the Nitrolite SDK doesn't provide its own transport layer, these examples show how to implement WebSocket connections and the NitroliteRPC message format in different frameworks. - - - - -```javascript -import { useState, useEffect, useCallback } from 'react'; -import { ethers } from 'ethers'; -import { - createAuthRequestMessage, - createAuthVerifyMessage, - createGetChannelsMessage, - createGetLedgerBalancesMessage, - createGetConfigMessage, - generateRequestId, - getCurrentTimestamp -} from '@erc7824/nitrolite'; - -// Custom hook for ClearNode connection -function useClearNodeConnection(clearNodeUrl, stateWallet) { - const [ws, setWs] = useState(null); - const [connectionStatus, setConnectionStatus] = useState('disconnected'); - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [error, setError] = useState(null); - - // Message signer function - const messageSigner = useCallback(async (payload) => { - if (!stateWallet) throw new Error('State wallet not available'); - - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }, [stateWallet]); - - // Create a signed request - const createSignedRequest = useCallback(async (method, params = []) => { - if (!stateWallet) throw new Error('State wallet not available'); - - const requestId = generateRequestId(); - const timestamp = getCurrentTimestamp(); - const requestData = [requestId, method, params, timestamp]; - const request = { req: requestData }; - - // Sign the request - const message = JSON.stringify(request); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - request.sig = [signature]; - - return JSON.stringify(request); - }, [stateWallet]); - - // Send a message to the ClearNode - const sendMessage = useCallback((message) => { - if (!ws || ws.readyState !== WebSocket.OPEN) { - setError('WebSocket not connected'); - return false; - } - - try { - ws.send(typeof message === 'string' ? message : JSON.stringify(message)); - return true; - } catch (error) { - setError(`Error sending message: ${error.message}`); - return false; - } - }, [ws]); - - // Connect to the ClearNode - const connect = useCallback(() => { - if (ws) { - ws.close(); - } - - setConnectionStatus('connecting'); - setError(null); - - const newWs = new WebSocket(clearNodeUrl); - - newWs.onopen = async () => { - setConnectionStatus('connected'); - - // Start authentication process - try { - const authRequest = await createAuthRequestMessage( - messageSigner, - stateWallet.address - ); - newWs.send(authRequest); - } catch (err) { - setError(`Authentication request failed: ${err.message}`); - } - }; - - newWs.onmessage = async (event) => { - try { - const message = JSON.parse(event.data); - - // Handle authentication flow - if (message.res && message.res[1] === 'auth_challenge') { - try { - const authVerify = await createAuthVerifyMessage( - messageSigner, - message, - stateWallet.address - ); - newWs.send(authVerify); - } catch (err) { - setError(`Authentication verification failed: ${err.message}`); - } - } else if (message.res && message.res[1] === 'auth_success') { - setIsAuthenticated(true); - } else if (message.res && message.res[1] === 'auth_failure') { - setIsAuthenticated(false); - setError(`Authentication failed: ${message.res[2]}`); - } - - // Additional message handling can be added here - } catch (err) { - console.error('Error handling message:', err); - } - }; - - newWs.onerror = (error) => { - setError(`WebSocket error: ${error.message}`); - setConnectionStatus('error'); - }; - - newWs.onclose = () => { - setConnectionStatus('disconnected'); - setIsAuthenticated(false); - }; - - setWs(newWs); - }, [clearNodeUrl, messageSigner, stateWallet]); - - // Disconnect from the ClearNode - const disconnect = useCallback(() => { - if (ws) { - ws.close(); - setWs(null); - } - }, [ws]); - - // Connect when the component mounts - useEffect(() => { - if (clearNodeUrl && stateWallet) { - connect(); - } - - // Clean up on unmount - return () => { - if (ws) { - ws.close(); - } - }; - }, [clearNodeUrl, stateWallet, connect]); - - // Create helper methods for common operations - const getChannels = useCallback(async () => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetChannelsMessage( - messageSigner, - stateWallet.address - ); - return sendMessage(message); - }, [messageSigner, sendMessage, stateWallet]); - - const getLedgerBalances = useCallback(async (channelId) => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetLedgerBalancesMessage( - messageSigner, - channelId - ); - return sendMessage(message); - }, [messageSigner, sendMessage]); - - const getConfig = useCallback(async () => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetConfigMessage( - messageSigner, - stateWallet.address - ); - return sendMessage(message); - }, [messageSigner, sendMessage, stateWallet]); - - return { - connectionStatus, - isAuthenticated, - error, - ws, - connect, - disconnect, - sendMessage, - getChannels, - getLedgerBalances, - getConfig, - createSignedRequest - }; -} - -// Example usage in a component -function ClearNodeComponent() { - const stateWallet = /* your state wallet initialization */; - const { - connectionStatus, - isAuthenticated, - error, - getChannels - } = useClearNodeConnection('wss://clearnode.example.com', stateWallet); - - return ( -
-

Status: {connectionStatus}

-

Authenticated: {isAuthenticated ? 'Yes' : 'No'}

- {error &&

Error: {error}

} - - -
- ); -} -``` - -
- - -```typescript -// In an Angular service (clearnode.service.ts) -import { Injectable } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { ethers } from 'ethers'; -import { - createAuthRequestMessage, - createAuthVerifyMessage, - createGetChannelsMessage, - createGetLedgerBalancesMessage, - createGetConfigMessage, - generateRequestId, - getCurrentTimestamp -} from '@erc7824/nitrolite'; - -@Injectable({ - providedIn: 'root' -}) -export class ClearNodeService { - private ws: WebSocket | null = null; - private connectionStatusSource = new BehaviorSubject('disconnected'); - private isAuthenticatedSource = new BehaviorSubject(false); - private errorSource = new BehaviorSubject(null); - private messageSubject = new BehaviorSubject(null); - - // Public observables - connectionStatus$ = this.connectionStatusSource.asObservable(); - isAuthenticated$ = this.isAuthenticatedSource.asObservable(); - error$ = this.errorSource.asObservable(); - message$ = this.messageSubject.asObservable(); - - // Custom message signer - private async messageSigner(payload: any, stateWallet: any): Promise { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - } - - // Create a signed request - async createSignedRequest(method: string, params: any[], stateWallet: any): Promise { - const requestId = generateRequestId(); - const timestamp = getCurrentTimestamp(); - const requestData = [requestId, method, params, timestamp]; - const request: any = { req: requestData }; - - // Sign the request - const message = JSON.stringify(request); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - request.sig = [signature]; - - return JSON.stringify(request); - } - - // Connect to the ClearNode - connect(url: string, stateWallet: any): void { - if (this.ws) { - this.ws.close(); - } - - this.connectionStatusSource.next('connecting'); - this.errorSource.next(null); - - this.ws = new WebSocket(url); - - this.ws.onopen = async () => { - this.connectionStatusSource.next('connected'); - - // Start authentication process - try { - const authRequest = await createAuthRequestMessage( - (payload) => this.messageSigner(payload, stateWallet), - stateWallet.address - ); - this.ws!.send(authRequest); - } catch (err: any) { - this.errorSource.next(`Authentication request failed: ${err.message}`); - } - }; - - this.ws.onmessage = async (event) => { - try { - const message = JSON.parse(event.data); - this.messageSubject.next(message); - - // Handle authentication flow - if (message.res && message.res[1] === 'auth_challenge') { - try { - const authVerify = await createAuthVerifyMessage( - (payload) => this.messageSigner(payload, stateWallet), - message, - stateWallet.address - ); - this.ws!.send(authVerify); - } catch (err: any) { - this.errorSource.next(`Authentication verification failed: ${err.message}`); - } - } else if (message.res && message.res[1] === 'auth_success') { - this.isAuthenticatedSource.next(true); - } else if (message.res && message.res[1] === 'auth_failure') { - this.isAuthenticatedSource.next(false); - this.errorSource.next(`Authentication failed: ${message.res[2]}`); - } - } catch (err: any) { - console.error('Error handling message:', err); - } - }; - - this.ws.onerror = (error) => { - this.errorSource.next(`WebSocket error`); - this.connectionStatusSource.next('error'); - }; - - this.ws.onclose = () => { - this.connectionStatusSource.next('disconnected'); - this.isAuthenticatedSource.next(false); - }; - } - - // Disconnect from the ClearNode - disconnect(): void { - if (this.ws) { - this.ws.close(); - this.ws = null; - } - } - - // Send a message to the ClearNode - sendMessage(message: string | object): boolean { - if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { - this.errorSource.next('WebSocket not connected'); - return false; - } - - try { - this.ws.send(typeof message === 'string' ? message : JSON.stringify(message)); - return true; - } catch (error: any) { - this.errorSource.next(`Error sending message: ${error.message}`); - return false; - } - } - - // Helper methods for common operations - async getChannels(stateWallet: any): Promise { - // Using the built-in helper function from NitroliteRPC - const message = await createGetChannelsMessage( - (payload) => this.messageSigner(payload, stateWallet), - stateWallet.address - ); - return this.sendMessage(message); - } - - async getLedgerBalances(channelId: string, stateWallet: any): Promise { - // Using the built-in helper function from NitroliteRPC - const message = await createGetLedgerBalancesMessage( - (payload) => this.messageSigner(payload, stateWallet), - channelId - ); - return this.sendMessage(message); - } - - async getConfig(stateWallet: any): Promise { - // Using the built-in helper function from NitroliteRPC - const message = await createGetConfigMessage( - (payload) => this.messageSigner(payload, stateWallet), - stateWallet.address - ); - return this.sendMessage(message); - } -} - -// Usage in a component -import { Component, OnInit, OnDestroy } from '@angular/core'; -import { ClearNodeService } from './clearnode.service'; -import { Subscription } from 'rxjs'; - -@Component({ - selector: 'app-clearnode', - template: ` -
-

Status: {{ connectionStatus }}

-

Authenticated: {{ isAuthenticated ? 'Yes' : 'No' }}

-

Error: {{ error }}

- - -
- ` -}) -export class ClearNodeComponent implements OnInit, OnDestroy { - connectionStatus = 'disconnected'; - isAuthenticated = false; - error: string | null = null; - - private subscriptions: Subscription[] = []; - private stateWallet: any; // Initialize your state wallet - - constructor(private clearNodeService: ClearNodeService) {} - - ngOnInit() { - this.subscriptions.push( - this.clearNodeService.connectionStatus$.subscribe( - status => this.connectionStatus = status - ), - this.clearNodeService.isAuthenticated$.subscribe( - auth => this.isAuthenticated = auth - ), - this.clearNodeService.error$.subscribe( - err => this.error = err - ), - this.clearNodeService.message$.subscribe( - message => { - if (message) { - console.log('Received message:', message); - // Handle specific message types here - } - } - ) - ); - - // Connect to ClearNode - this.clearNodeService.connect('wss://clearnode.example.com', this.stateWallet); - } - - ngOnDestroy() { - this.clearNodeService.disconnect(); - this.subscriptions.forEach(sub => sub.unsubscribe()); - } - - getChannels() { - this.clearNodeService.getChannels(this.stateWallet); - } -} -``` - -
- - -```javascript -// In a Vue component or Composable -import { ref, onMounted, onUnmounted } from 'vue'; -import { ethers } from 'ethers'; -import { - createAuthRequestMessage, - createAuthVerifyMessage, - createGetChannelsMessage, - createGetLedgerBalancesMessage, - createGetConfigMessage, - generateRequestId, - getCurrentTimestamp -} from '@erc7824/nitrolite'; - -// ClearNode connection composable -export function useClearNodeConnection(clearNodeUrl, stateWallet) { - const ws = ref(null); - const connectionStatus = ref('disconnected'); - const isAuthenticated = ref(false); - const error = ref(null); - const messages = ref([]); - - // Message signer function - const messageSigner = async (payload) => { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - }; - - // Create a signed request - const createSignedRequest = async (method, params = []) => { - const requestId = generateRequestId(); - const timestamp = getCurrentTimestamp(); - const requestData = [requestId, method, params, timestamp]; - const request = { req: requestData }; - - // Sign the request - const message = JSON.stringify(request); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = stateWallet.signingKey.sign(messageBytes); - request.sig = [signature]; - - return JSON.stringify(request); - }; - - // Send a message to the ClearNode - const sendMessage = (message) => { - if (!ws.value || ws.value.readyState !== WebSocket.OPEN) { - error.value = 'WebSocket not connected'; - return false; - } - - try { - ws.value.send(typeof message === 'string' ? message : JSON.stringify(message)); - return true; - } catch (err) { - error.value = `Error sending message: ${err.message}`; - return false; - } - }; - - // Connect to the ClearNode - const connect = () => { - if (ws.value) { - ws.value.close(); - } - - connectionStatus.value = 'connecting'; - error.value = null; - - const newWs = new WebSocket(clearNodeUrl); - - newWs.onopen = async () => { - connectionStatus.value = 'connected'; - - // Start authentication process - try { - const authRequest = await createAuthRequestMessage( - messageSigner, - stateWallet.address - ); - newWs.send(authRequest); - } catch (err) { - error.value = `Authentication request failed: ${err.message}`; - } - }; - - newWs.onmessage = async (event) => { - try { - const message = JSON.parse(event.data); - messages.value.push(message); - - // Handle authentication flow - if (message.res && message.res[1] === 'auth_challenge') { - try { - const authVerify = await createAuthVerifyMessage( - messageSigner, - message, - stateWallet.address - ); - newWs.send(authVerify); - } catch (err) { - error.value = `Authentication verification failed: ${err.message}`; - } - } else if (message.res && message.res[1] === 'auth_success') { - isAuthenticated.value = true; - } else if (message.res && message.res[1] === 'auth_failure') { - isAuthenticated.value = false; - error.value = `Authentication failed: ${message.res[2]}`; - } - } catch (err) { - console.error('Error handling message:', err); - } - }; - - newWs.onerror = (err) => { - error.value = 'WebSocket error'; - connectionStatus.value = 'error'; - }; - - newWs.onclose = () => { - connectionStatus.value = 'disconnected'; - isAuthenticated.value = false; - }; - - ws.value = newWs; - }; - - // Disconnect from the ClearNode - const disconnect = () => { - if (ws.value) { - ws.value.close(); - ws.value = null; - } - }; - - // Helper methods for common operations - const getChannels = async () => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetChannelsMessage( - messageSigner, - stateWallet.address - ); - return sendMessage(message); - }; - - const getLedgerBalances = async (channelId) => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetLedgerBalancesMessage( - messageSigner, - channelId - ); - return sendMessage(message); - }; - - const getConfig = async () => { - // Using the built-in helper function from NitroliteRPC - const message = await createGetConfigMessage( - messageSigner, - stateWallet.address - ); - return sendMessage(message); - }; - - // Connect and disconnect with the component lifecycle - onMounted(() => { - if (clearNodeUrl && stateWallet) { - connect(); - } - }); - - onUnmounted(() => { - disconnect(); - }); - - return { - connectionStatus, - isAuthenticated, - error, - messages, - connect, - disconnect, - sendMessage, - getChannels, - getLedgerBalances, - getConfig, - createSignedRequest - }; -} - -// Example usage in a Vue component -export default { - setup() { - // Initialize your state wallet - const stateWallet = {}; - - const { - connectionStatus, - isAuthenticated, - error, - messages, - getChannels, - getLedgerBalances, - getConfig - } = useClearNodeConnection('wss://clearnode.example.com', stateWallet); - - return { - connectionStatus, - isAuthenticated, - error, - messages, - getChannels, - getLedgerBalances, - getConfig - }; - } -}; -``` - - - - -```javascript -const WebSocket = require('ws'); -const { ethers } = require('ethers'); -const { - createAuthRequestMessage, - createAuthVerifyMessage, - createGetLedgerBalancesMessage, - createGetConfigMessage, - generateRequestId, - getCurrentTimestamp -} = require('@erc7824/nitrolite'); -const EventEmitter = require('events'); - -class ClearNodeConnection extends EventEmitter { - constructor(url, stateWallet) { - super(); - this.url = url; - this.stateWallet = stateWallet; - this.ws = null; - this.isConnected = false; - this.isAuthenticated = false; - this.reconnectAttempts = 0; - this.maxReconnectAttempts = 5; - this.reconnectInterval = 3000; // ms - this.requestMap = new Map(); // Track pending requests - } - - // Message signer function - async messageSigner(payload) { - try { - const message = JSON.stringify(payload); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = this.stateWallet.signingKey.sign(messageBytes); - return signature; - } catch (error) { - console.error("Error signing message:", error); - throw error; - } - } - - // Create a signed request - async createSignedRequest(method, params = [], requestId = generateRequestId()) { - const timestamp = getCurrentTimestamp(); - const requestData = [requestId, method, params, timestamp]; - const request = { req: requestData }; - - // Sign the request - const message = JSON.stringify(request); - const digestHex = ethers.id(message); - const messageBytes = ethers.getBytes(digestHex); - const { serialized: signature } = this.stateWallet.signingKey.sign(messageBytes); - request.sig = [signature]; - - return { request, requestId }; - } - - // Connect to the ClearNode - async connect() { - return new Promise((resolve, reject) => { - if (this.ws) { - this.ws.close(); - } - - this.emit('connecting'); - - this.ws = new WebSocket(this.url); - - // Set connection timeout - const connectionTimeout = setTimeout(() => { - if (!this.isConnected) { - this.ws.close(); - reject(new Error('Connection timeout')); - } - }, 10000); - - this.ws.on('open', async () => { - clearTimeout(connectionTimeout); - this.isConnected = true; - this.reconnectAttempts = 0; - this.emit('connected'); - - // Start authentication - try { - const authRequest = await createAuthRequestMessage( - (payload) => this.messageSigner(payload), - this.stateWallet.address - ); - - this.ws.send(authRequest); - // Do not resolve here, wait for auth_success - } catch (error) { - this.emit('error', `Authentication request failed: ${error.message}`); - reject(error); - } - }); - - this.ws.on('message', async (data) => { - try { - const message = JSON.parse(data); - this.emit('message', message); - - // Handle authentication flow - if (message.res && message.res[1] === 'auth_challenge') { - try { - const authVerify = await createAuthVerifyMessage( - (payload) => this.messageSigner(payload), - message, - this.stateWallet.address - ); - - this.ws.send(authVerify); - } catch (error) { - this.emit('error', `Authentication verification failed: ${error.message}`); - reject(error); - } - } else if (message.res && message.res[1] === 'auth_success') { - this.isAuthenticated = true; - this.emit('authenticated'); - resolve(); // Authentication successful - } else if (message.res && message.res[1] === 'auth_failure') { - this.isAuthenticated = false; - const error = new Error(`Authentication failed: ${message.res[2]}`); - this.emit('error', error.message); - reject(error); - } - - // Handle other response types - if (message.res && message.res[0]) { - const requestId = message.res[0]; - const handler = this.requestMap.get(requestId); - if (handler) { - handler.resolve(message); - this.requestMap.delete(requestId); - } - } - } catch (error) { - console.error('Error handling message:', error); - } - }); - - this.ws.on('error', (error) => { - clearTimeout(connectionTimeout); - this.emit('error', `WebSocket error: ${error.message}`); - reject(error); - }); - - this.ws.on('close', (code, reason) => { - clearTimeout(connectionTimeout); - this.isConnected = false; - this.isAuthenticated = false; - this.emit('disconnected', { code, reason: reason.toString() }); - - // Attempt to reconnect - this.attemptReconnect(); - }); - }); - } - - // Send a request and wait for the response - async sendRequest(method, params = []) { - if (!this.isConnected || !this.isAuthenticated) { - throw new Error('Not connected or authenticated'); - } - - const { request, requestId } = await this.createSignedRequest(method, params); - - return new Promise((resolve, reject) => { - // Set up response handler - const timeout = setTimeout(() => { - this.requestMap.delete(requestId); - reject(new Error(`Request timeout for ${method}`)); - }, 30000); - - this.requestMap.set(requestId, { - resolve: (response) => { - clearTimeout(timeout); - resolve(response); - }, - reject, - timeout - }); - - // Send the request - try { - this.ws.send(JSON.stringify(request)); - } catch (error) { - clearTimeout(timeout); - this.requestMap.delete(requestId); - reject(error); - } - }); - } - - // Helper methods for common operations - async getChannels() { - // Using the built-in helper function from NitroliteRPC - const message = await createGetChannelsMessage( - (payload) => this.messageSigner(payload), - this.stateWallet.address - ); - - return new Promise((resolve, reject) => { - try { - const parsed = JSON.parse(message); - const requestId = parsed.req[0]; - - const timeout = setTimeout(() => { - this.requestMap.delete(requestId); - reject(new Error('Request timeout for getChannels')); - }, 30000); - - this.requestMap.set(requestId, { - resolve: (response) => { - clearTimeout(timeout); - resolve(response); - }, - reject, - timeout - }); - - this.ws.send(message); - } catch (error) { - reject(error); - } - }); - } - - async getLedgerBalances(channelId) { - // Using the built-in helper function from NitroliteRPC - const message = await createGetLedgerBalancesMessage( - (payload) => this.messageSigner(payload), - channelId - ); - - return new Promise((resolve, reject) => { - try { - const parsed = JSON.parse(message); - const requestId = parsed.req[0]; - - const timeout = setTimeout(() => { - this.requestMap.delete(requestId); - reject(new Error('Request timeout for getLedgerBalances')); - }, 30000); - - this.requestMap.set(requestId, { - resolve: (response) => { - clearTimeout(timeout); - resolve(response); - }, - reject, - timeout - }); - - this.ws.send(message); - } catch (error) { - reject(error); - } - }); - } - - async getConfig() { - // Using the built-in helper function from NitroliteRPC - const message = await createGetConfigMessage( - (payload) => this.messageSigner(payload), - this.stateWallet.address - ); - - return new Promise((resolve, reject) => { - try { - const parsed = JSON.parse(message); - const requestId = parsed.req[0]; - - const timeout = setTimeout(() => { - this.requestMap.delete(requestId); - reject(new Error('Request timeout for getConfig')); - }, 30000); - - this.requestMap.set(requestId, { - resolve: (response) => { - clearTimeout(timeout); - resolve(response); - }, - reject, - timeout - }); - - this.ws.send(message); - } catch (error) { - reject(error); - } - }); - } - - // Attempt to reconnect with exponential backoff - attemptReconnect() { - if (this.reconnectAttempts >= this.maxReconnectAttempts) { - this.emit('error', 'Maximum reconnection attempts reached'); - return; - } - - this.reconnectAttempts++; - const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1); - - this.emit('reconnecting', { attempt: this.reconnectAttempts, delay }); - - setTimeout(() => { - this.connect().catch(error => { - console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error); - }); - }, delay); - } - - // Disconnect from the ClearNode - disconnect() { - if (this.ws) { - // Clear all pending requests - for (const [requestId, handler] of this.requestMap.entries()) { - clearTimeout(handler.timeout); - handler.reject(new Error('Connection closed')); - this.requestMap.delete(requestId); - } - - this.ws.close(1000, 'User initiated disconnect'); - this.ws = null; - } - } -} - -// Example usage -async function main() { - // Initialize your state wallet (this is just a placeholder) - const privateKey = '0x1234...'; // Your private key - const stateWallet = new ethers.Wallet(privateKey); - - // Create a ClearNode connection - const clearNode = new ClearNodeConnection('wss://clearnode.example.com', stateWallet); - - // Set up event handlers - clearNode.on('connecting', () => { - console.log('Connecting to ClearNode...'); - }); - - clearNode.on('connected', () => { - console.log('Connected to ClearNode'); - }); - - clearNode.on('authenticated', () => { - console.log('Authenticated with ClearNode'); - }); - - clearNode.on('disconnected', ({ code, reason }) => { - console.log(`Disconnected from ClearNode: ${code} ${reason}`); - }); - - clearNode.on('error', (error) => { - console.error(`ClearNode error: ${error}`); - }); - - clearNode.on('reconnecting', ({ attempt, delay }) => { - console.log(`Reconnecting (${attempt}/${clearNode.maxReconnectAttempts}) in ${delay}ms...`); - }); - - try { - // Connect and authenticate - await clearNode.connect(); - console.log('Successfully connected and authenticated'); - - // Get channels - const channels = await clearNode.getChannels(); - console.log('Channels:', channels.res[2][0]); - - // Process the channels - const channelList = channels.res[2][0]; - if (channelList && channelList.length > 0) { - for (const channel of channelList) { - console.log(`Channel ID: ${channel.channel_id}`); - console.log(`Status: ${channel.status}`); - console.log(`Token: ${channel.token}`); - - // Get ledger balances for the channel - if (channel.status === 'open') { - const balances = await clearNode.getLedgerBalances(channel.channel_id); - console.log(`Balances:`, balances.res[2]); - } - } - } else { - console.log('No channels found'); - } - } catch (error) { - console.error('Error:', error); - } finally { - // Disconnect when done - clearNode.disconnect(); - } -} - -// Handle process termination -process.on('SIGINT', () => { - console.log('Shutting down...'); - // Clean up resources here - process.exit(0); -}); - -// Run the example -main().catch(console.error); -``` - - -
- - -## Security Considerations - -When working with ClearNodes and state channels, keep these security best practices in mind: - -1. **Secure State Wallet Storage**: Properly encrypt and secure the private key for your state wallet -2. **Verify Message Signatures**: Always verify that received messages have valid signatures from expected sources -3. **Monitor Connection Status**: Implement monitoring to detect unexpected disconnections or authentication failures -4. **Implement Timeout Handling**: Add timeouts for operations to prevent hanging on unresponsive connections -5. **Validate Channel States**: Verify that channel states are valid before processing or saving them -6. **Use Secure WebSocket Connections**: Always use `wss://` (WebSocket Secure) for ClearNode connections, never `ws://` -7. **Implement Rate Limiting**: Add protection against excessive message sending to prevent abuse - -## Troubleshooting Common Issues - -| Issue | Possible Causes | Solution | -| ------------------------- | ----------------------------------------- | ---------------------------------------------------------------------- | -| Connection timeout | Network latency, ClearNode unavailable | Implement retry logic with exponential backoff | -| Authentication failure | Invalid state wallet, incorrect signing | Verify your state wallet is properly initialized and signing correctly | -| Frequent disconnections | Unstable network, server-side issues | Monitor connection events and implement automatic reconnection | -| Message delivery failures | Connection issues, invalid message format | Add message queuing and confirmation mechanism | -| Invalid signature errors | EIP-191 prefix issues | Ensure you're signing raw message bytes without the EIP-191 prefix | - -## Next Steps - -After successfully connecting to a ClearNode, you can: - -1. [View and manage channel assets](balances) -2. [Create an application session](application_session) diff --git a/erc7824-docs/docs/quick_start/index.mdx b/erc7824-docs/docs/quick_start/index.mdx deleted file mode 100644 index bed5781ac..000000000 --- a/erc7824-docs/docs/quick_start/index.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -sidebar_position: 1 -title: Quick Start -description: Learn how to use Nitrolite state channels from start to finish. -keywords: [erc7824, nitrolite, state channels, quick start, tutorial] ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -# Quick Start - -**[Nitrolite](https://www.npmjs.com/package/@erc7824/nitrolite)** is our official SDK for creating high-performance decentralized applications. It provides a comprehensive set of functions and types to establish WebSocket connections with ClearNode and manage application sessions. - -## Prerequisites - -Before you begin working with Nitrolite, ensure that you have: - -- **Node.js**: Version 16 or later -- **Package Manager**: npm, yarn, or pnpm -- **Development Environment**: - - For frontend: React, Vue, or similar framework - - For backend: Node.js environment -- **Channel Setup**: Create a channel from your account at [apps.yellow.com](https://apps.yellow.com) - -## Installation - -You can install Nitrolite using your preferred package manager: - - - - - ```bash - npm install @erc7824/nitrolite - ``` - - - - - ```bash - yarn add @erc7824/nitrolite - ``` - - - - - ```bash - pnpm add @erc7824/nitrolite - ``` - - - - - -## ClearNode WebSocket - -**ClearNode WebSocket URL**: `wss://clearnet.yellow.com/ws` - - -## Build with AI -We have generated a [llms-full.txt](https://erc7824.org/llms-full.txt) file that converts all our documentation into a single markdown document following the https://llmstxt.org/ standard. - - - -## Complete Workflow - -```mermaid -graph TD - A[Initialize Channel] --> B[Connect to ClearNode] - B --> C[Create Application Session] - C --> D[Perform Operations] - D --> E[Close Session] -``` - -## Next steps - -Building applications with Nitrolite involves these key steps: - -1. **[Channel Creation](initializing_channel)**: Create a channel from your account at apps.yellow.com -2. **[ClearNode Connection](connect_to_the_clearnode)**: Establish WebSocket connection for off-chain messaging -3. **[Application Sessions](application_session)**: Create sessions to run specific applications -4. **[Session Closure](close_session)**: Properly close application sessions when finished - -We recommend working through these guides in sequence to understand the complete application workflow. Each guide builds on concepts from previous sections. - -Start with the [Channel Creation](initializing_channel) guide to begin your journey with Nitrolite applications. diff --git a/erc7824-docs/docs/quick_start/initializing_channel.md b/erc7824-docs/docs/quick_start/initializing_channel.md deleted file mode 100644 index 973622f95..000000000 --- a/erc7824-docs/docs/quick_start/initializing_channel.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -sidebar_position: 2 -title: Create a Channel -description: Create and configure a channel from your account at apps.yellow.com -keywords: [channel creation, apps.yellow.com, setup, configuration] ---- - -# Create a Channel - -Before you can start building with Nitrolite, you need to create a channel from your account. This is done through [apps.yellow.com](https://apps.yellow.com), similar to how you would set up an app in other development platforms. - -## Getting Started - -To get started, visit [apps.yellow.com](https://apps.yellow.com) and create an account. You can use the platform to manage your apps, configurations, channels, and more. - - -## Creating Your First Channel - -1. **Sign up or log in** to [apps.yellow.com](https://apps.yellow.com) -2. **Navigate to your channels** where you can manage them -3. **Create a new channel** by clicking the appropriate button - -That's it! After creating your channel, you can start building your app with your business logic. You don't need to worry about channel credentials - the ClearNode will handle channel identification automatically when you connect. - -## Channel Configuration - -When creating a channel, you'll be able to configure: - -- **Channel name and description** for easy identification -- **Application settings** specific to your use case -- **Access permissions** and participant management -- **Integration settings** for your development environment - -## Next Steps - -Once your channel is created at [apps.yellow.com](https://apps.yellow.com), you're ready to: - -1. **[Start building your App](connect_to_the_clearnode)** - Connect to ClearNode and begin development -2. **[Create application sessions](application_session)** to implement your business logic diff --git a/erc7824-docs/docusaurus.config.js b/erc7824-docs/docusaurus.config.js deleted file mode 100644 index 470a3bbd6..000000000 --- a/erc7824-docs/docusaurus.config.js +++ /dev/null @@ -1,276 +0,0 @@ -// @ts-check -// `@type` JSDoc annotations allow editor autocompletion and type checking -// (when paired with `@ts-check`). -// There are various equivalent ways to declare your Docusaurus config. -// See: https://docusaurus.io/docs/api/docusaurus-config - -import { themes as prismThemes } from "prism-react-renderer"; -import fs from "node:fs"; -import path from "node:path"; - -// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: "ERC7824 State channels framework", - tagline: "Statechannels norm and framework using nitro protocol", - favicon: "img/favicon.ico", - - // Set the production url of your site here - url: "https://erc7824.org", - // Set the // pathname under which your site is served - // For GitHub pages deployment, it is often '//' - baseUrl: "/", - - // GitHub pages deployment config. - // If you aren't using GitHub pages, you don't need these. - organizationName: "erc7824", // Usually your GitHub org/user name. - projectName: "nitro", // Usually your repo name. - - onBrokenLinks: "throw", - onBrokenMarkdownLinks: "warn", - - // Even if you don't use internationalization, you can use this field to set - // useful metadata like html lang. For example, if your site is Chinese, you - // may want to replace "en" with "zh-Hans". - i18n: { - defaultLocale: "en", - locales: ["en"], - }, - plugins: [ - async function pluginLlmsTxt(context) { - return { - name: "llms-txt-plugin", - loadContent: async () => { - const { siteDir } = context; - const contentDir = path.join(siteDir, "docs"); - const allMdx = []; - - // recursive function to get all mdx files - const getMdxFiles = async (dir) => { - const entries = await fs.promises.readdir(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - await getMdxFiles(fullPath); - } else if ((entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) && fullPath.includes("quick_start")) { - const content = await fs.promises.readFile(fullPath, "utf8"); - allMdx.push({ path: fullPath, content }); - } - } - }; - - // Define the correct order of quick start guides - const quickStartOrder = [ - "initializing_client", - "deposit_and_create_channel", - "connect_to_the_clearnode", - "balances", - "application_session", - "close_session", - "resize_channel", - "close_channel", - "withdrawal", - "index", // Keep index page last - ]; - - // Sort function to order the quick start guides - const sortQuickStartFiles = (files) => { - return files.sort((a, b) => { - // Extract the base filename without extension - const getBaseName = (path) => { - const filename = path.split("/").pop(); - return filename.split(".")[0]; - }; - - const aName = getBaseName(a.path); - const bName = getBaseName(b.path); - - // Get the index in the quickStartOrder array - const aIndex = quickStartOrder.indexOf(aName); - const bIndex = quickStartOrder.indexOf(bName); - - // If both exist in the order array, sort by that order - if (aIndex !== -1 && bIndex !== -1) { - return aIndex - bIndex; - } - - // If only one exists, prioritize it - if (aIndex !== -1) return -1; - if (bIndex !== -1) return 1; - - // If neither exists in the order array, maintain alphabetical order - return aName.localeCompare(bName); - }); - }; - - await getMdxFiles(contentDir); - - // Sort the files based on our ordering - const sortedMdx = sortQuickStartFiles(allMdx); - - // Extract just the content in the correct order - const orderedContent = sortedMdx.map((item) => item.content); - - return { allMdx: orderedContent }; - }, - postBuild: async ({ content, routes, outDir }) => { - const { allMdx } = content; - - // Write concatenated MDX content - const concatenatedPath = path.join(outDir, "llms-full.txt"); - await fs.promises.writeFile(concatenatedPath, allMdx.join("\n\n---\n\n")); - - // we need to dig down several layers: - // find PluginRouteConfig marked by plugin.name === "docusaurus-plugin-content-docs" - const docsPluginRouteConfig = routes.filter((route) => route.plugin.name === "docusaurus-plugin-content-docs")[0]; - - // docsPluginRouteConfig has a routes property has a record with the path "/" that contains all docs routes. - const allDocsRouteConfig = docsPluginRouteConfig.routes?.filter((route) => route.path === "/")[0]; - - // A little type checking first - if (!allDocsRouteConfig?.props?.version) { - return; - } - - // this route config has a `props` property that contains the current documentation. - const currentVersionDocsRoutes = allDocsRouteConfig.props.version.docs; - - // for every single docs route we now parse a path (which is the key) and a title - const docsRecords = Object.entries(currentVersionDocsRoutes).map(([path, record]) => { - return `- [${record.title}](${path}): ${record.description}`; - }); - - // Build up llms.txt file - const llmsTxt = `# ${context.siteConfig.title}\n\n## Docs\n\n${docsRecords.join("\n")}`; - - // Write llms.txt file - const llmsTxtPath = path.join(outDir, "llms.txt"); - try { - fs.writeFileSync(llmsTxtPath, llmsTxt); - } catch (err) { - throw err; - } - }, - }; - }, - ], - - // mermaid support - markdown: { - mermaid: true, - }, - themes: ["@docusaurus/theme-mermaid"], - - presets: [ - [ - "classic", - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - routeBasePath: "/", - sidebarPath: "./sidebars.js", - breadcrumbs: false, - }, - blog: false, - theme: { - customCss: "./src/css/custom.css", - }, - }), - ], - ], - - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - // Replace with your project's social card - image: "img/erc7824_social_card.png", - navbar: { - logo: { - alt: "ERC-7824", - src: "img/logo.svg", - srcDark: "img/logo_dark.svg", - }, - items: [ - { - type: "search", - position: "right", - }, - { - href: "https://github.com/erc7824", - label: "GitHub", - position: "right", - }, - // { - // type: 'html', - // position: 'right', - // value: 'Learn', - // }, - ], - }, - footer: { - style: "dark", - links: [ - { - title: "Docs", - items: [ - { label: "Introduction", to: "/" }, - - ], - }, - { - title: "Community", - items: [ - { - label: "X", - href: "https://x.com/Yellow", - }, - { - label: "Telegram", - href: "https://t.me/yellow_org", - }, - { - label: "Discord", - href: "https://discord.gg/yellownetwork", - }, - ], - }, - { - title: "More", - items: [ - { - label: "GitHub", - href: "https://github.com/erc7824/nitro", - }, - ], - }, - ], - copyright: `Copyright © ${new Date().getFullYear()}, Layer 3 Foundation. ERC-7824 is under MIT License`, - }, - prism: { - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - additionalLanguages: ["solidity"], - magicComments: [ - { - className: 'git-diff-remove', - line: 'remove-next-line', - block: { start: 'remove-start', end: 'remove-end' }, - }, - { - className: 'git-diff-add', - line: 'add-next-line', - block: { start: 'add-start', end: 'add-end' }, - }, - { - className: 'theme-code-block-highlighted-line', - line: 'highlight-next-line', - block: { start: 'highlight-start', end: 'highlight-end' }, - }, - ], - }, - }), -}; - -export default config; diff --git a/erc7824-docs/firebase.json b/erc7824-docs/firebase.json deleted file mode 100644 index f47555498..000000000 --- a/erc7824-docs/firebase.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "hosting": { - "public": "build", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ] - } -} diff --git a/erc7824-docs/package-lock.json b/erc7824-docs/package-lock.json deleted file mode 100644 index b21b4a3b9..000000000 --- a/erc7824-docs/package-lock.json +++ /dev/null @@ -1,19685 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "website", - "version": "0.0.0", - "dependencies": { - "@docusaurus/core": "^3.9.2", - "@docusaurus/preset-classic": "^3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", - "@mdx-js/react": "^3.1.1", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^19.2.3", - "react-dom": "^19.2.3" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.7.0", - "@docusaurus/types": "3.7.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@ai-sdk/gateway": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.9.tgz", - "integrity": "sha512-E6x4h5CPPPJ0za1r5HsLtHbeI+Tp3H+YFtcH8G3dSSPFE6w+PZINzB4NxLZmg1QqSeA5HTP3ZEzzsohp0o2GEw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@vercel/oidc": "3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", - "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz", - "integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/react": { - "version": "2.0.93", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.93.tgz", - "integrity": "sha512-2TzhpQr10HuWxpqyHpSAUMRUqD1G2O73J2sAaJChomVDbjr7BwpM0mdR3aRamCXNtuLiJmTFQhbNzw8fXMBdYw==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider-utils": "3.0.17", - "ai": "5.0.93", - "swr": "^2.2.5", - "throttleit": "2.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "zod": "^3.25.76 || ^4.1.8" - }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } - } - }, - "node_modules/@ai-sdk/react/node_modules/ai": { - "version": "5.0.93", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.93.tgz", - "integrity": "sha512-9eGcu+1PJgPg4pRNV4L7tLjRR3wdJC9CXQoNMvtqvYNOLZHFCzjHtVIOr2SIkoJJeu2+sOy3hyiSuTmy2MA40g==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "2.0.9", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@algolia/abtesting": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.10.0.tgz", - "integrity": "sha512-mQT3jwuTgX8QMoqbIR7mPlWkqQqBPQaPabQzm37xg2txMlaMogK/4hCiiESGdg39MlHZOVHeV+0VJuE7f5UK8A==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", - "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", - "@algolia/autocomplete-shared": "1.19.2" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", - "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-shared": "1.19.2" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", - "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", - "license": "MIT", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.44.0.tgz", - "integrity": "sha512-KY5CcrWhRTUo/lV7KcyjrZkPOOF9bjgWpMj9z98VA+sXzVpZtkuskBLCKsWYFp2sbwchZFTd3wJM48H0IGgF7g==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.44.0.tgz", - "integrity": "sha512-LKOCE8S4ewI9bN3ot9RZoYASPi8b78E918/DVPW3HHjCMUe6i+NjbNG6KotU4RpP6AhRWZjjswbOkWelUO+OoA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.44.0.tgz", - "integrity": "sha512-1yyJm4OYC2cztbS28XYVWwLXdwpLsMG4LoZLOltVglQ2+hc/i9q9fUDZyjRa2Bqt4DmkIfezagfMrokhyH4uxQ==", - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.44.0.tgz", - "integrity": "sha512-wVQWK6jYYsbEOjIMI+e5voLGPUIbXrvDj392IckXaCPvQ6vCMTXakQqOYCd+znQdL76S+3wHDo77HZWiAYKrtA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.44.0.tgz", - "integrity": "sha512-lkgRjOjOkqmIkebHjHpU9rLJcJNUDMm+eVSW/KJQYLjGqykEZxal+nYJJTBbLceEU2roByP/+27ZmgIwCdf0iA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.44.0.tgz", - "integrity": "sha512-sYfhgwKu6NDVmZHL1WEKVLsOx/jUXCY4BHKLUOcYa8k4COCs6USGgz6IjFkUf+niwq8NCECMmTC4o/fVQOalsA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.44.0.tgz", - "integrity": "sha512-/FRKUM1G4xn3vV8+9xH1WJ9XknU8rkBGlefruq9jDhYUAvYozKimhrmC2pRqw/RyHhPivmgZCRuC8jHP8piz4Q==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/events": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", - "license": "MIT" - }, - "node_modules/@algolia/ingestion": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.44.0.tgz", - "integrity": "sha512-5+S5ynwMmpTpCLXGjTDpeIa81J+R4BLH0lAojOhmeGSeGEHQTqacl/4sbPyDTcidvnWhaqtyf8m42ue6lvISAw==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.44.0.tgz", - "integrity": "sha512-xhaTN8pXJjR6zkrecg4Cc9YZaQK2LKm2R+LkbAq+AYGBCWJxtSGlNwftozZzkUyq4AXWoyoc0x2SyBtq5LRtqQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.44.0.tgz", - "integrity": "sha512-GNcite/uOIS7wgRU1MT7SdNIupGSW+vbK9igIzMePvD2Dl8dy0O3urKPKIbTuZQqiVH1Cb84y5cgLvwNrdCj/Q==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.44.0.tgz", - "integrity": "sha512-YZHBk72Cd7pcuNHzbhNzF/FbbYszlc7JhZlDyQAchnX5S7tcemSS96F39Sy8t4O4WQLpFvUf1MTNedlitWdOsQ==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.44.0.tgz", - "integrity": "sha512-B9WHl+wQ7uf46t9cq+vVM/ypVbOeuldVDq9OtKsX2ApL2g/htx6ImB9ugDOOJmB5+fE31/XPTuCcYz/j03+idA==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.44.0.tgz", - "integrity": "sha512-MULm0qeAIk4cdzZ/ehJnl1o7uB5NMokg83/3MKhPq0Pk7+I0uELGNbzIfAkvkKKEYcHALemKdArtySF9eKzh/A==", - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@antfu/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-TMilPqXyii1AsiEii6l6ubRzbo76p6oshUSYPaKsmXDavyMLqjzVDkcp3pHp5ELMUNJHATcEOGxKTTsX9yYhGg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", - "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", - "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", - "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", - "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.28.0", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", - "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.43.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", - "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", - "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.1.1", - "@chevrotain/types": "11.1.1", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", - "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.1.1", - "lodash-es": "4.17.23" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", - "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", - "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", - "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", - "license": "Apache-2.0" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/postcss-alpha-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", - "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", - "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", - "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-function-display-p3-linear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", - "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", - "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", - "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", - "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-contrast-color-function": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", - "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", - "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", - "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", - "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", - "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", - "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", - "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", - "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", - "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", - "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", - "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", - "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", - "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", - "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", - "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", - "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", - "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", - "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", - "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/core": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.3.1.tgz", - "integrity": "sha512-ktVbkePE+2h9RwqCUMbWXOoebFyDOxHqImAqfs+lC8yOU+XwEW4jgvHGJK079deTeHtdhUNj0PXHSnhJINvHzQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/@docsearch/css": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.3.2.tgz", - "integrity": "sha512-K3Yhay9MgkBjJJ0WEL5MxnACModX9xuNt3UlQQkDEDZJZ0+aeWKtOkxHNndMRkMBnHdYvQjxkm6mdlneOtU1IQ==", - "license": "MIT" - }, - "node_modules/@docsearch/react": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.3.2.tgz", - "integrity": "sha512-74SFD6WluwvgsOPqifYOviEEVwDxslxfhakTlra+JviaNcs7KK/rjsPj89kVEoQc9FUxRkAofaJnHIR7pb4TSQ==", - "license": "MIT", - "dependencies": { - "@ai-sdk/react": "^2.0.30", - "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.3.1", - "@docsearch/css": "4.3.2", - "ai": "^5.0.30", - "algoliasearch": "^5.28.0", - "marked": "^16.3.0", - "zod": "^4.1.8" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 20.0.0", - "react": ">= 16.8.0 < 20.0.0", - "react-dom": ">= 16.8.0 < 20.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@docusaurus/babel": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz", - "integrity": "sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/runtime-corejs3": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/bundler": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz", - "integrity": "sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.9.2", - "@docusaurus/cssnano-preset": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.3", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.2", - "null-loader": "^4.0.1", - "postcss": "^8.5.4", - "postcss-loader": "^7.3.4", - "postcss-preset-env": "^10.2.1", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^6.0.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } - } - }, - "node_modules/@docusaurus/bundler/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/bundler/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/core": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", - "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", - "license": "MIT", - "dependencies": { - "@docusaurus/babel": "3.9.2", - "@docusaurus/bundler": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "execa": "5.1.1", - "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.6", - "tinypool": "^1.0.2", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz", - "integrity": "sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==", - "license": "MIT", - "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.5.4", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/logger": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz", - "integrity": "sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz", - "integrity": "sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^2.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz", - "integrity": "sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==", - "dev": true, - "dependencies": { - "@docusaurus/types": "3.7.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@*", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz", - "integrity": "sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "cheerio": "1.0.0-rc.12", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-blog/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-blog/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", - "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "schema-dts": "^1.1.2", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz", - "integrity": "sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-pages/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz", - "integrity": "sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/plugin-css-cascade-layers/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-css-cascade-layers/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz", - "integrity": "sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^2.3.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-debug/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-debug/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz", - "integrity": "sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz", - "integrity": "sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@types/gtag.js": "^0.0.12", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz", - "integrity": "sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-tag-manager/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-tag-manager/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz", - "integrity": "sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/plugin-svgr": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz", - "integrity": "sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@svgr/core": "8.1.0", - "@svgr/webpack": "^8.1.0", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-svgr/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/plugin-svgr/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/preset-classic": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz", - "integrity": "sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/plugin-content-blog": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/plugin-content-pages": "3.9.2", - "@docusaurus/plugin-css-cascade-layers": "3.9.2", - "@docusaurus/plugin-debug": "3.9.2", - "@docusaurus/plugin-google-analytics": "3.9.2", - "@docusaurus/plugin-google-gtag": "3.9.2", - "@docusaurus/plugin-google-tag-manager": "3.9.2", - "@docusaurus/plugin-sitemap": "3.9.2", - "@docusaurus/plugin-svgr": "3.9.2", - "@docusaurus/theme-classic": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-search-algolia": "3.9.2", - "@docusaurus/types": "3.9.2" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/preset-classic/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/theme-classic": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz", - "integrity": "sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/plugin-content-blog": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/plugin-content-pages": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-translations": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "infima": "0.2.0-alpha.45", - "lodash": "^4.17.21", - "nprogress": "^0.2.0", - "postcss": "^8.5.4", - "prism-react-renderer": "^2.3.0", - "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", - "rtlcss": "^4.1.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-classic/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/theme-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", - "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", - "license": "MIT", - "dependencies": { - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-common/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/theme-mermaid": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz", - "integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "mermaid": ">=11.6.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@mermaid-js/layout-elk": "^0.1.9", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@mermaid-js/layout-elk": { - "optional": true - } - } - }, - "node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/theme-mermaid/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-mermaid/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz", - "integrity": "sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.9.0 || ^4.1.0", - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-translations": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "algoliasearch": "^5.37.0", - "algoliasearch-helper": "^3.26.0", - "clsx": "^2.0.0", - "eta": "^2.2.0", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-translations": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz", - "integrity": "sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==", - "license": "MIT", - "dependencies": { - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/types": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz", - "integrity": "sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==", - "dev": true, - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/types/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/utils": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", - "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "escape-string-regexp": "^4.0.0", - "execa": "5.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "github-slugger": "^1.5.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "jiti": "^1.20.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "p-queue": "^6.6.2", - "prompts": "^2.4.2", - "resolve-pathname": "^3.0.0", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz", - "integrity": "sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-common/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/utils-common/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz", - "integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils/node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/utils/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", - "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@antfu/utils": "^9.2.0", - "@iconify/types": "^2.0.0", - "debug": "^4.4.1", - "globals": "^15.15.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.1.1", - "mlly": "^1.7.4" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/buffers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz", - "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz", - "integrity": "sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.1", - "@jsonjoy.com/util": "^1.9.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pointer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/util": "^1.9.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", - "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mdx-js/react": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", - "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", - "license": "MIT", - "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, - "node_modules/@mermaid-js/parser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", - "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", - "license": "MIT", - "dependencies": { - "langium": "^4.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.28", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", - "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@slorber/remark-comment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.1.0", - "micromark-util-symbol": "^1.0.1" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/acorn": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", - "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/gtag.js": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" - }, - "node_modules/@types/node": { - "version": "22.13.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz", - "integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prismjs": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", - "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", - "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-config": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", - "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "^5.1.0" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "license": "MIT" - }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" - }, - "node_modules/@vercel/oidc": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.3.tgz", - "integrity": "sha512-yNEQvPcVrK9sIe637+I0jD6leluPxzwJKx/Haw6F4H77CdDsszUn5V3o96LPziXkSNE2B83+Z3mjqGKBK/R6Gg==", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ai": { - "version": "5.0.95", - "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.95.tgz", - "integrity": "sha512-dsvFdYMeGP08zuUQkhKO1UMMXMCb+nro9ZmDdwaAkkTlCGkP3u1S+xaRUDNayu/c0KVkiTtfEroPG//U+kvXzg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "2.0.11", - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ai/node_modules/@ai-sdk/gateway": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.11.tgz", - "integrity": "sha512-B0Vt2Xv88Lo9rg861Oyzq/SdTmT4LiqdjkpOxpSPpNk8Ih5TXTgyDAsV/qW14N6asPdK1YI5PosFLnVzfK5LrA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.17", - "@vercel/oidc": "3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/algoliasearch": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.44.0.tgz", - "integrity": "sha512-f8IpsbdQjzTjr/4mJ/jv5UplrtyMnnciGax6/B0OnLCs2/GJTK13O4Y7Ff1AvJVAaztanH+m5nzPoUq6EAy+aA==", - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.10.0", - "@algolia/client-abtesting": "5.44.0", - "@algolia/client-analytics": "5.44.0", - "@algolia/client-common": "5.44.0", - "@algolia/client-insights": "5.44.0", - "@algolia/client-personalization": "5.44.0", - "@algolia/client-query-suggestions": "5.44.0", - "@algolia/client-search": "5.44.0", - "@algolia/ingestion": "1.44.0", - "@algolia/monitoring": "1.44.0", - "@algolia/recommend": "5.44.0", - "@algolia/requester-browser-xhr": "5.44.0", - "@algolia/requester-fetch": "5.44.0", - "@algolia/requester-node-http": "5.44.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/algoliasearch-helper": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.1.tgz", - "integrity": "sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw==", - "license": "MIT", - "dependencies": { - "@algolia/events": "^4.0.1" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "license": "MIT", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "license": "MIT" - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001770", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", - "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chevrotain": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", - "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.1.1", - "@chevrotain/gast": "11.1.1", - "@chevrotain/regexp-to-ast": "11.1.1", - "@chevrotain/types": "11.1.1", - "@chevrotain/utils": "11.1.1", - "lodash-es": "4.17.23" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/combine-promises": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "license": "ISC" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compressible/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "license": "MIT" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-js": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", - "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", - "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", - "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", - "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", - "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - } - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssdb": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz", - "integrity": "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ], - "license": "MIT-0" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", - "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, - "node_modules/dayjs": { - "version": "1.11.18", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", - "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", - "license": "MIT" - }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, - "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/emoticon": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", - "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-value-to-estree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", - "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eta": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", - "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", - "dependencies": { - "@types/node": "*", - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/express/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "license": "MIT" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/feed": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", - "license": "MIT", - "dependencies": { - "xml-js": "^1.6.11" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/file-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/file-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "license": "MIT", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "license": "ISC" - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-slugger": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regex.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz", - "integrity": "sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.1.tgz", - "integrity": "sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", - "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript/node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "node_modules/html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/html-webpack-plugin/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "license": "MIT", - "engines": { - "node": ">=10.18" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/infima": { - "version": "0.2.0-alpha.45", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", - "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/katex": { - "version": "0.16.22", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", - "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "license": "MIT" - }, - "node_modules/langium": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", - "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.1.1", - "chevrotain-allstar": "~0.3.1", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.1.0" - }, - "engines": { - "node": ">=20.10.0", - "npm": ">=10.2.3" - } - }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", - "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "4.46.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.46.1.tgz", - "integrity": "sha512-2wjHDg7IjP+ufAqqqSxjiNePFDrvWviA79ajUwG9lkHhk3HzZOLBzzoUG8cx9vCagj6VvBQD7oXuLuFz5LaAOQ==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.0.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/mermaid": { - "version": "11.12.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", - "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^1.0.0", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.23", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", - "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", - "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", - "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", - "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", - "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-space/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", - "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", - "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", - "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", - "license": "MIT" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/null-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/null-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", - "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", - "license": "MIT" - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-numeric-range": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", - "license": "ISC" - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", - "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-custom-media": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", - "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", - "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", - "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-focus-within": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-lab-function": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", - "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.3.5", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-logical": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", - "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-merge-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", - "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz", - "integrity": "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-alpha-function": "^1.0.1", - "@csstools/postcss-cascade-layers": "^5.0.2", - "@csstools/postcss-color-function": "^4.0.12", - "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", - "@csstools/postcss-color-mix-function": "^3.0.12", - "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", - "@csstools/postcss-content-alt-text": "^2.0.8", - "@csstools/postcss-contrast-color-function": "^2.0.12", - "@csstools/postcss-exponential-functions": "^2.0.9", - "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.11", - "@csstools/postcss-gradients-interpolation-method": "^5.0.12", - "@csstools/postcss-hwb-function": "^4.0.12", - "@csstools/postcss-ic-unit": "^4.0.4", - "@csstools/postcss-initial": "^2.0.1", - "@csstools/postcss-is-pseudo-class": "^5.0.3", - "@csstools/postcss-light-dark-function": "^2.0.11", - "@csstools/postcss-logical-float-and-clear": "^3.0.0", - "@csstools/postcss-logical-overflow": "^2.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", - "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.4", - "@csstools/postcss-media-minmax": "^2.0.9", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", - "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.12", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/postcss-random-function": "^2.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.12", - "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.4", - "@csstools/postcss-stepped-value-functions": "^4.0.9", - "@csstools/postcss-text-decoration-shorthand": "^4.0.3", - "@csstools/postcss-trigonometric-functions": "^4.0.9", - "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.21", - "browserslist": "^4.26.0", - "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.3", - "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.4.2", - "postcss-attribute-case-insensitive": "^7.0.1", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.12", - "postcss-color-hex-alpha": "^10.0.0", - "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.6", - "postcss-custom-properties": "^14.0.6", - "postcss-custom-selectors": "^8.0.5", - "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.4", - "postcss-focus-visible": "^10.0.1", - "postcss-focus-within": "^9.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^6.0.0", - "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.12", - "postcss-logical": "^8.1.0", - "postcss-nesting": "^13.0.2", - "postcss-opacity-percentage": "^3.0.0", - "postcss-overflow-shorthand": "^6.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^10.0.0", - "postcss-pseudo-class-any-link": "^10.0.1", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^8.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-reduce-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", - "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sort-media-queries": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", - "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", - "license": "MIT", - "dependencies": { - "sort-css-media-queries": "2.2.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.23" - } - }, - "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" - }, - "engines": { - "node": "^14 || ^16 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/postcss-zindex": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", - "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/prism-react-renderer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", - "dependencies": { - "@types/prismjs": "^1.26.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.0.0" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.3" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "node_modules/react-helmet-async": { - "name": "@slorber/react-helmet-async", - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", - "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "invariant": "^2.2.4", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.2.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-json-view-lite": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", - "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/react-loadable": { - "name": "@docusaurus/react-loadable", - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", - "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", - "dependencies": { - "@types/react": "*" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "dependencies": { - "@babel/runtime": "^7.10.3" - }, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "react-loadable": "*", - "webpack": ">=4.41.1 || 5.x" - } - }, - "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "dependencies": { - "@babel/runtime": "^7.1.2" - }, - "peerDependencies": { - "react": ">=15", - "react-router": ">=5" - } - }, - "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", - "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-emoji": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", - "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.2", - "emoticon": "^4.0.1", - "mdast-util-find-and-replace": "^3.0.1", - "node-emoji": "^2.1.0", - "unified": "^11.0.4" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/remark-frontmatter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-frontmatter": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", - "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", - "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/renderkid/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/renderkid/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", - "engines": { - "node": "*" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/rtlcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", - "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0", - "postcss": "^8.4.21", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", - "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", - "license": "BlueOak-1.0.0" - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/schema-dts": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", - "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", - "license": "Apache-2.0" - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "license": "MIT", - "peer": true - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/sitemap": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", - "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", - "license": "MIT", - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=5.6.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/sort-css-media-queries": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", - "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", - "license": "MIT", - "engines": { - "node": ">= 6.3.0" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/srcset": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-to-object": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", - "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", - "dependencies": { - "inline-style-parser": "0.2.4" - } - }, - "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/swr": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", - "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", - "license": "MIT", - "engines": { - "node": ">=10.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "^2" - } - }, - "node_modules/throttleit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", - "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", - "license": "MIT" - }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/tree-dump": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/url-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/url-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT" - }, - "node_modules/url-loader/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "license": "MIT" - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpack": { - "version": "5.105.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", - "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", - "es-module-lexer": "^2.0.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", - "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.43.1", - "mime-types": "^3.0.1", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", - "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/express-serve-static-core": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", - "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.9", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.4.2", - "ws": "^8.18.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-dev-server/node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", - "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wsl-utils/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "license": "MIT", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", - "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/erc7824-docs/package.json b/erc7824-docs/package.json deleted file mode 100644 index 2ddd7daa3..000000000 --- a/erc7824-docs/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "^3.9.2", - "@docusaurus/preset-classic": "^3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", - "@mdx-js/react": "^3.1.1", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^19.2.3", - "react-dom": "^19.2.3" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.7.0", - "@docusaurus/types": "3.7.0" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 3 chrome version", - "last 3 firefox version", - "last 5 safari version" - ] - }, - "engines": { - "node": ">=18.0" - } -} diff --git a/erc7824-docs/sidebars.js b/erc7824-docs/sidebars.js deleted file mode 100644 index f77355c3e..000000000 --- a/erc7824-docs/sidebars.js +++ /dev/null @@ -1,35 +0,0 @@ -// @ts-check - -// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) - -/** - * Creating a sidebar enables you to: - - create an ordered group of docs - - render a sidebar for each doc of that group - - provide next/previous navigation - - The sidebars can be generated from the filesystem, or explicitly defined here. - - Create as many sidebars as you want. - - @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} - */ -const sidebars = { - // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], - - // But you can create a sidebar manually - /* - tutorialSidebar: [ - 'intro', - 'hello', - { - type: 'category', - label: 'Tutorial', - items: ['tutorial-basics/create-a-document'], - }, - ], - */ -}; - -export default sidebars; diff --git a/erc7824-docs/src/components/Card/index.js b/erc7824-docs/src/components/Card/index.js deleted file mode 100644 index 9fc809f8c..000000000 --- a/erc7824-docs/src/components/Card/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import Link from '@docusaurus/Link'; -import clsx from 'clsx'; -import styles from './styles.module.css'; - -export function Card({ title, description, to, icon }) { - return ( - -
- {icon &&
{icon}
} -
-

{title}

-
- {description && ( -

{description}

- )} -
- - ); -} - -export function CardGrid({ children, cols = 2 }) { - return ( -
- {children} -
- ); -} \ No newline at end of file diff --git a/erc7824-docs/src/components/Card/styles.module.css b/erc7824-docs/src/components/Card/styles.module.css deleted file mode 100644 index eddff449b..000000000 --- a/erc7824-docs/src/components/Card/styles.module.css +++ /dev/null @@ -1,79 +0,0 @@ -.cardGrid { - display: grid; - gap: 1rem; - margin-bottom: 2rem; -} - -.cols2 { - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); -} - -.cols3 { - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); -} - -.cols4 { - grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); -} - -.card { - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - transition: all 0.2s ease; - overflow: hidden; - text-decoration: none !important; - color: var(--ifm-font-color-base); - display: flex; - flex-direction: column; - height: 100%; - background-color: var(--ifm-card-background-color); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.card:hover { - border-color: var(--ifm-color-primary); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - transform: translateY(-2px); -} - -.cardContent { - padding: 1.5rem; - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.cardHeader { - margin-bottom: 0.5rem; -} - -.cardTitle { - font-size: 1.2rem; - margin-bottom: 0.5rem; - color: var(--ifm-heading-color); -} - -.cardDescription { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-700); - margin: 0; - flex-grow: 1; -} - -.cardIcon { - margin-bottom: 1rem; - display: flex; - align-items: center; - justify-content: center; - height: 2.5rem; - width: 2.5rem; - border-radius: 8px; - background-color: var(--ifm-color-primary-lightest); - color: var(--ifm-color-primary-dark); -} - -@media (max-width: 768px) { - .cardGrid { - grid-template-columns: 1fr; - } -} \ No newline at end of file diff --git a/erc7824-docs/src/components/HomepageFeatures/index.js b/erc7824-docs/src/components/HomepageFeatures/index.js deleted file mode 100644 index acc762199..000000000 --- a/erc7824-docs/src/components/HomepageFeatures/index.js +++ /dev/null @@ -1,64 +0,0 @@ -import clsx from 'clsx'; -import Heading from '@theme/Heading'; -import styles from './styles.module.css'; - -const FeatureList = [ - { - title: 'Easy to Use', - Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), - }, - { - title: 'Focus on What Matters', - Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, - description: ( - <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. - - ), - }, - { - title: 'Powered by React', - Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), - }, -]; - -function Feature({Svg, title, description}) { - return ( -
-
- -
-
- {title} -

{description}

-
-
- ); -} - -export default function HomepageFeatures() { - return ( -
-
-
- {FeatureList.map((props, idx) => ( - - ))} -
-
-
- ); -} diff --git a/erc7824-docs/src/components/HomepageFeatures/styles.module.css b/erc7824-docs/src/components/HomepageFeatures/styles.module.css deleted file mode 100644 index b248eb2e5..000000000 --- a/erc7824-docs/src/components/HomepageFeatures/styles.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.featureSvg { - height: 200px; - width: 200px; -} diff --git a/erc7824-docs/src/components/MethodCard.tsx b/erc7824-docs/src/components/MethodCard.tsx deleted file mode 100644 index 351d5fb6f..000000000 --- a/erc7824-docs/src/components/MethodCard.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; - -interface Param { - name: string; - type: string; -} - -interface MethodCardProps { - name: string; - description: string; - params?: Param[]; - returns?: string; - example?: string; -} - -const MethodCard: React.FC = ({ - name, - description, - params = [], - returns, - example, -}) => { - return ( -
-
-

- {name} -

-

- {description} -

-
- -
- {params.length > 0 && ( -
-

- Parameters -

-
    - {params.map((param, index) => ( -
  • - - {param.name}: - {' '} - - {param.type} - -
  • - ))} -
-
- )} - - {returns && ( -
-

- Returns -

- - {returns} - -
- )} -
- - {example && ( -
-

- Example -

-
-            {example}
-          
-
- )} -
- ); -}; - -export default MethodCard; \ No newline at end of file diff --git a/erc7824-docs/src/components/MethodDetails.tsx b/erc7824-docs/src/components/MethodDetails.tsx deleted file mode 100644 index 23424bda2..000000000 --- a/erc7824-docs/src/components/MethodDetails.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React from "react"; -import Link from "@docusaurus/Link"; -import CodeBlock from "@theme/CodeBlock"; - -// Common custom types that should link to the types documentation -// Map type names to their corresponding anchors in the types.md page - must match the actual heading IDs -const TYPE_ANCHORS = { - CreateChannelParams: "#2-channel-creation", - CloseChannelParams: "#4-channel-closing", - CheckpointChannelParams: "#3-channel-operations", - ChallengeChannelParams: "#3-channel-operations", - ResizeChannelParams: "#3-channel-operations", - ChannelId: "#channelid", - State: "#state", - StateIntent: "#stateintent", - Allocation: "#allocation", - Signature: "#signature", - AccountInfo: "#accountinfo", - Hash: "#core-types", - PreparedTransaction: "#preparedtransaction", - ContractAddresses: "#contractaddresses", - NitroliteClientConfig: "#nitroliteclientconfig", -}; - -// Get all type names for checking -const CUSTOM_TYPES = Object.keys(TYPE_ANCHORS); - -interface Param { - name: string; - type: string; -} - -interface MethodDetailsProps { - name: string; - description: string; - params?: Param[]; - returns?: string; - example?: string; -} - -const MethodDetails: React.FC = ({ name, description, params = [], returns, example }) => { - // Function to render a type with a link if it's a custom type - const renderType = (type: string) => { - // Extract the base type from arrays, promises, etc. - let baseType = type; - - // Extract from Promise - const promiseMatch = type.match(/Promise<(.+)>/); - if (promiseMatch) baseType = promiseMatch[1]; - - // Handle complex nested types - baseType = baseType.replace(/\{.*\}/, ""); // Remove object literals - baseType = baseType.replace(/\[.*\]/, ""); // Remove array literals - - // Check if any custom type is found in the type string - const foundCustomType = CUSTOM_TYPES.find((customType) => baseType.includes(customType)); - - if (foundCustomType) { - const anchor = TYPE_ANCHORS[foundCustomType]; - return ( - - {type.split(foundCustomType).map((part, i, parts) => ( - - {part} - {i < parts.length - 1 && ( - - {foundCustomType} - - )} - - ))} - - ); - } - - return {type}; - }; - - return ( -
- {name} -
-

{description}

- - {params.length > 0 && ( -
-

Parameters

-
    - {params.map((param, index) => ( -
  • - {param.name}: - {renderType(param.type)} -
  • - ))} -
-
- )} - - {returns && ( -

- Returns: - {renderType(returns)} -

- )} - - {example && ( -
-

Example

-
- - {example} - -
-
- )} -
-
- ); -}; - -// Add styles for the summary icon rotation and code formatting -const styles = ` - details[open] .summary-icon { - transform: rotate(90deg); - } - - .method-example-code pre { - tab-size: 2; - -moz-tab-size: 2; - white-space: pre !important; - } - - .method-example-code code { - white-space: pre !important; - } -`; - -// Add the styles to the document -if (typeof document !== "undefined") { - const styleElement = document.createElement("style"); - styleElement.textContent = styles; - document.head.appendChild(styleElement); -} - -export default MethodDetails; diff --git a/erc7824-docs/src/css/custom.css b/erc7824-docs/src/css/custom.css deleted file mode 100644 index 362e65749..000000000 --- a/erc7824-docs/src/css/custom.css +++ /dev/null @@ -1,213 +0,0 @@ -/** - * Global CSS overrides for Docusaurus. - * Infima is bundled by default. - */ - -:root { - --ifm-color-primary: #000; - --ifm-color-primary-dark: #111; - --ifm-color-primary-darker: #222; - --ifm-color-primary-darkest: #333; - --ifm-color-primary-light: #444; - --ifm-color-primary-lighter: #555; - --ifm-color-primary-lightest: #666; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.05); - --ifm-link-color: #3531ff; - --ifm-footer-link-hover-color: #3531ff; -} - -[data-theme="dark"] { - --ifm-color-primary: #fff; - --ifm-color-primary-dark: #eee; - --ifm-color-primary-darker: #ddd; - --ifm-color-primary-darkest: #ccc; - --ifm-color-primary-light: #bbb; - --ifm-color-primary-lighter: #aaa; - --ifm-color-primary-lightest: #999; - --docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.1); - --ifm-link-color: #a09fff; - --ifm-link-hover-color: #c3c1ff; - --ifm-footer-link-hover-color: #c3c1ff; - --ifm-background-color: #090909 !important; -} - -[data-theme="light"] .footer { - border-top: 1px solid var(--ifm-toc-border-color); - --ifm-footer-background-color: #fff !important; - --ifm-footer-link-color: var(--ifm-color-gray-700); - --ifm-footer-title-color: var(--ifm-color-dark); -} - -[data-theme="dark"] .navbar { - background-color: #090909 !important; - --ifm-navbar-background-color: #090909 !important; -} - -h1 { - font-size: 2.25em; -} - -h2 { - font-size: 1.5em; -} - -a:not([class]):hover { - text-decoration: underline; -} - -.footer--dark { - border-top: 1px solid #333; - --ifm-footer-background-color: #000; -} - -[data-theme="light"] .logo [fill="#000"] { - fill: #000; -} - -[data-theme="dark"] .logo [fill="#000"] { - fill: #fff; -} - -.logo { - width: -webkit-fill-available; - height: 70px; - padding-top: 16px; - margin-top: 8px; -} - -.alert--info { - --ifm-alert-background-color: #f5f5f5; - --ifm-alert-background-color-highlight: rgba(0, 0, 0, 0.05); - --ifm-alert-foreground-color: #333333; - --ifm-alert-border-color: var(--ifm-toc-border-color); - - background-color: var(--ifm-alert-background-color); - color: var(--ifm-alert-foreground-color); -} - -[data-theme="dark"] .alert--info { - --ifm-alert-background-color: #1a1a1a; - --ifm-alert-background-color-highlight: rgba(255, 255, 255, 0.1); - --ifm-alert-foreground-color: #f5f5f5; - --ifm-alert-border-color: var(--ifm-toc-border-color); - - background-color: var(--ifm-alert-background-color); - color: var(--ifm-alert-foreground-color); -} - -.tabs__item { - margin-top: var(--ifm-list-item-margin); -} - -@media (min-width: 1416px) { - html.docs-doc-page .main-wrapper { - align-self: center; - max-width: 82rem; - width: 82rem; - } -} - -.navbar .navbar__inner { - margin: 0 auto; - max-width: 82rem; -} - -details { - margin-bottom: 1rem; - border: 1px solid #ddd; - padding: 0.5rem; - border-radius: 4px; -} - -summary { - cursor: pointer; - font-weight: bold; -} - -details[open] summary { - color: var(--ifm-color-primary); -} - -/* Custom button styling */ -.custom-button { - background-color: #000; - color: #fff !important; - border: none; - padding: 8px 16px; - border-radius: 4px; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; - display: inline-block; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.custom-button:hover { - opacity: 0.85; - transform: translateY(-1px); - text-decoration: none; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); -} - -.custom-button:active { - transform: translateY(0); -} - -/* Dark mode button styling */ -[data-theme="dark"] .custom-button { - background-color: #fff; - color: #000 !important; - box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1); -} - -[data-theme="dark"] .custom-button:hover { - box-shadow: 0 4px 8px rgba(255, 255, 255, 0.15); -} - -/* Position navbar items with flexbox order */ -.navbar__items--right { - display: flex; -} - -/* Give the theme toggle a lower order */ -.navbar__items--right > .dropdown { - order: 10; -} - -/* Make sure search is first */ -.navbar__items--right > *:first-child { - order: 5; -} - -/* Give our custom button the highest order to ensure it's last */ -.navbar__items--right > *:nth-child(3) { - order: 100; -} - -.git-diff-remove { - background-color: rgba(255, 0, 0, 0.125); - border-left: 3px solid rgba(255, 0, 0, 0.5); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -.git-diff-add { - background-color: rgba(26, 255, 0, 0.125); - border-left: 3px solid rgba(26, 255, 0, 0.5); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -[data-theme="dark"] .git-diff-remove { - background-color: rgba(255, 0, 0, 0.15); - border-left: 3px solid rgba(255, 0, 0, 0.6); -} - -[data-theme="dark"] .git-diff-add { - background-color: rgba(26, 255, 0, 0.15); - border-left: 3px solid rgba(26, 255, 0, 0.6); -} diff --git a/erc7824-docs/src/pages/index.module.css b/erc7824-docs/src/pages/index.module.css deleted file mode 100644 index 9f71a5da7..000000000 --- a/erc7824-docs/src/pages/index.module.css +++ /dev/null @@ -1,23 +0,0 @@ -/** - * CSS files with the .module.css suffix will be treated as CSS modules - * and scoped locally. - */ - -.heroBanner { - padding: 4rem 0; - text-align: center; - position: relative; - overflow: hidden; -} - -@media screen and (max-width: 996px) { - .heroBanner { - padding: 2rem; - } -} - -.buttons { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/erc7824-docs/src/pages/markdown-page.md b/erc7824-docs/src/pages/markdown-page.md deleted file mode 100644 index 9756c5b66..000000000 --- a/erc7824-docs/src/pages/markdown-page.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Markdown page example ---- - -# Markdown page example - -You don't need React to write simple standalone pages. diff --git a/erc7824-docs/static/.nojekyll b/erc7824-docs/static/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/erc7824-docs/static/img/erc7824.png b/erc7824-docs/static/img/erc7824.png deleted file mode 100644 index 49a38d000..000000000 Binary files a/erc7824-docs/static/img/erc7824.png and /dev/null differ diff --git a/erc7824-docs/static/img/erc7824_social_card.png b/erc7824-docs/static/img/erc7824_social_card.png deleted file mode 100644 index 06b3a9ce2..000000000 Binary files a/erc7824-docs/static/img/erc7824_social_card.png and /dev/null differ diff --git a/erc7824-docs/static/img/favicon.ico b/erc7824-docs/static/img/favicon.ico deleted file mode 100644 index e49844ed9..000000000 Binary files a/erc7824-docs/static/img/favicon.ico and /dev/null differ diff --git a/erc7824-docs/static/img/logo.svg b/erc7824-docs/static/img/logo.svg deleted file mode 100644 index 57826ed27..000000000 --- a/erc7824-docs/static/img/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/erc7824-docs/static/img/logo_dark.svg b/erc7824-docs/static/img/logo_dark.svg deleted file mode 100644 index f921bf8f8..000000000 --- a/erc7824-docs/static/img/logo_dark.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/erc7824-docs/static/img/undraw_docusaurus_mountain.svg b/erc7824-docs/static/img/undraw_docusaurus_mountain.svg deleted file mode 100644 index af961c49a..000000000 --- a/erc7824-docs/static/img/undraw_docusaurus_mountain.svg +++ /dev/null @@ -1,171 +0,0 @@ - - Easy to Use - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/erc7824-docs/static/img/undraw_docusaurus_react.svg b/erc7824-docs/static/img/undraw_docusaurus_react.svg deleted file mode 100644 index 94b5cf08f..000000000 --- a/erc7824-docs/static/img/undraw_docusaurus_react.svg +++ /dev/null @@ -1,170 +0,0 @@ - - Powered by React - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/erc7824-docs/static/img/undraw_docusaurus_tree.svg b/erc7824-docs/static/img/undraw_docusaurus_tree.svg deleted file mode 100644 index d9161d339..000000000 --- a/erc7824-docs/static/img/undraw_docusaurus_tree.svg +++ /dev/null @@ -1,40 +0,0 @@ - - Focus on What Matters - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/go.mod b/go.mod index 79cccd3e5..ec95cec81 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,22 @@ -module github.com/erc7824/nitrolite +module github.com/layer-3/nitrolite go 1.25.0 require ( cloud.google.com/go/kms v1.26.0 github.com/c-bata/go-prompt v0.2.6 - github.com/ethereum/go-ethereum v1.17.0 + github.com/ethereum/go-ethereum v1.17.1 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 github.com/jsternberg/zap-logfmt v1.3.0 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - github.com/testcontainers/testcontainers-go v0.40.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 - golang.org/x/term v0.40.0 - google.golang.org/api v0.269.0 + github.com/testcontainers/testcontainers-go v0.41.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.41.0 + go.yaml.in/yaml/v2 v2.4.4 + golang.org/x/term v0.41.0 + google.golang.org/api v0.271.0 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 @@ -32,21 +33,20 @@ require ( github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect github.com/mattn/go-tty v0.0.3 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect github.com/stretchr/objx v0.5.2 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/time v0.14.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect - google.golang.org/grpc v1.79.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/grpc v1.79.2 // indirect ) require ( @@ -69,11 +69,11 @@ require ( github.com/deckarep/golang-set/v2 v2.8.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.1+incompatible // indirect + github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect - github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.6 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -100,7 +100,7 @@ require ( github.com/mfridman/interpolate v0.0.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/go-archive v0.2.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect @@ -120,23 +120,23 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/shirou/gopsutil/v4 v4.26.2 // indirect github.com/shopspring/decimal v1.4.0 github.com/sirupsen/logrus v1.9.3 // indirect - github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect - github.com/tklauser/go-sysconf v0.3.15 // indirect - github.com/tklauser/numcpus v0.10.0 // indirect + github.com/supranational/blst v0.3.16 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 + go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 golang.org/x/crypto v0.48.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index de1b65258..8609fccd6 100644 --- a/go.sum +++ b/go.sum @@ -93,16 +93,16 @@ github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiD github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= -github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= @@ -110,12 +110,12 @@ github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3re github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= -github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= -github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs= +github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls= +github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk= github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8= -github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kRlAVxes= -github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o= +github.com/ethereum/go-ethereum v1.17.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho= +github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= @@ -160,8 +160,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -263,8 +263,8 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= -github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= @@ -337,8 +337,8 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= +github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -350,18 +350,18 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= -github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE= +github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk= -github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0/go.mod h1:h+u/2KoREGTnTl9UwrQ/g+XhasAT8E6dClclAADeXoQ= -github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= -github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= -github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= -github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/testcontainers/testcontainers-go v0.41.0 h1:mfpsD0D36YgkxGj2LrIyxuwQ9i2wCKAD+ESsYM1wais= +github.com/testcontainers/testcontainers-go v0.41.0/go.mod h1:pdFrEIfaPl24zmBjerWTTYaY0M6UHsqA1YSvsoU40MI= +github.com/testcontainers/testcontainers-go/modules/postgres v0.41.0 h1:AOtFXssrDlLm84A2sTTR/AhvJiYbrIuCO59d+Ro9Tb0= +github.com/testcontainers/testcontainers-go/modules/postgres v0.41.0/go.mod h1:k2a09UKhgSp6vNpliIY0QSgm4Hi7GXVTzWvWgUemu/8= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= @@ -374,20 +374,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -396,19 +396,19 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -424,26 +424,26 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg= -google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE= +google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY= +google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 h1:7ei4lp52gK1uSejlA8AZl5AJjeLUOHBQscRQZUgAcu0= google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20/go.mod h1:ZdbssH/1SOVnjnDlXzxDHK2MCidiqXtbYccJNzNYPEE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..cbe649e9e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,84 @@ +{ + "name": "nitrolite", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "^3.8.1", + "prettier-plugin-solidity": "^2.3.1" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.8.tgz", + "integrity": "sha512-wS5kg8u0KCML1UeHQPJ1IuOI24x/XLentCzsqPER1+gDNC5Cz2hG4G2blLOZap+3CEGhIhnJ9mmZYj6a2W0Lww==", + "dev": true, + "license": "(Apache-2.0 WITH LLVM-exception)" + }, + "node_modules/@nomicfoundation/slang": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-1.3.4.tgz", + "integrity": "sha512-ghzrPSYH1sZO65id6+Bq2Ood87HT54QP3RGC8EkmpcrJ6tT9Ky0RtaJfrzV5G4jpDsnNua6+YEDpzOMori04hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.17.2" + } + }, + "node_modules/@solidity-parser/parser": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.20.2.tgz", + "integrity": "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-2.3.1.tgz", + "integrity": "sha512-71sZM5oqgq6pnTlf+RH23U6Ej710APfCiMWO2Z/pHNjrXyvn9Nr0vTS1AUVaSf4GRW0V6hj6Djt0MyWudJUJbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/slang": "1.3.4", + "@solidity-parser/parser": "^0.20.2", + "semver": "^7.7.4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "prettier": ">=3.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..14905241c --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "prettier": "^3.8.1", + "prettier-plugin-solidity": "^2.3.1" + } +} diff --git a/pkg/app/app_session_v1.go b/pkg/app/app_session_v1.go index effb11875..f83df6277 100644 --- a/pkg/app/app_session_v1.go +++ b/pkg/app/app_session_v1.go @@ -2,11 +2,14 @@ package app import ( "fmt" + "strconv" + "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/core" "github.com/shopspring/decimal" ) @@ -37,6 +40,19 @@ func (intent AppStateUpdateIntent) String() string { } } +func (intent AppStateUpdateIntent) GatedAction() core.GatedAction { + switch intent { + case AppStateUpdateIntentOperate: + return core.GatedActionAppSessionOperation + case AppStateUpdateIntentDeposit: + return core.GatedActionAppSessionDeposit + case AppStateUpdateIntentWithdraw: + return core.GatedActionAppSessionWithdrawal + default: + return "" + } +} + type AppSessionStatus uint8 const ( @@ -58,18 +74,57 @@ func (status AppSessionStatus) String() string { } } +func (s *AppSessionStatus) Scan(src any) error { + switch v := src.(type) { + case int64: + *s = AppSessionStatus(uint8(v)) + return nil + case int32: + *s = AppSessionStatus(uint8(v)) + return nil + case int: + *s = AppSessionStatus(uint8(v)) + return nil + case string: + return s.scanString(v) + default: + return fmt.Errorf("unsupported AppSessionStatus scan type %T", src) + } +} + +func (s *AppSessionStatus) scanString(v string) error { + v = strings.TrimSpace(v) + // if numeric + if n, err := strconv.Atoi(v); err == nil { + *s = AppSessionStatus(uint8(n)) + return nil + } + // else map names + switch strings.ToLower(v) { + case AppSessionStatusVoid.String(): + *s = AppSessionStatusVoid + case AppSessionStatusOpen.String(): + *s = AppSessionStatusOpen + case AppSessionStatusClosed.String(): + *s = AppSessionStatusClosed + default: + return fmt.Errorf("unknown AppSessionStatus %q", v) + } + return nil +} + // AppSessionV1 represents an application session in the V1 API. type AppSessionV1 struct { - SessionID string - Application string - Participants []AppParticipantV1 - Quorum uint8 - Nonce uint64 - Status AppSessionStatus - Version uint64 - SessionData string - CreatedAt time.Time - UpdatedAt time.Time + SessionID string + ApplicationID string + Participants []AppParticipantV1 + Quorum uint8 + Nonce uint64 + Status AppSessionStatus + Version uint64 + SessionData string + CreatedAt time.Time + UpdatedAt time.Time } // AppParticipantV1 represents the definition for an app participant. @@ -80,10 +135,10 @@ type AppParticipantV1 struct { // AppDefinitionV1 represents the definition for an app session. type AppDefinitionV1 struct { - Application string - Participants []AppParticipantV1 - Quorum uint8 - Nonce uint64 + ApplicationID string + Participants []AppParticipantV1 + Quorum uint8 + Nonce uint64 } // AppSessionVersionV1 represents a session ID and version pair for rebalancing operations. @@ -116,14 +171,12 @@ type SignedAppStateUpdateV1 struct { // AppSessionInfoV1 represents information about an application session. type AppSessionInfoV1 struct { - AppSessionID string - IsClosed bool - Participants []AppParticipantV1 - SessionData string - Quorum uint8 - Version uint64 - Nonce uint64 - Allocations []AppAllocationV1 + AppSessionID string + AppDefinition AppDefinitionV1 + IsClosed bool + SessionData string + Version uint64 + Allocations []AppAllocationV1 } // SessionKeyV1 represents a session key with spending allowances. @@ -183,7 +236,7 @@ func PackCreateAppSessionRequestV1(definition AppDefinitionV1, sessionData strin // Pack the data using ABI encoding packed, err := args.Pack( - definition.Application, + definition.ApplicationID, participants, definition.Quorum, definition.Nonce, @@ -294,7 +347,7 @@ func GenerateAppSessionIDV1(definition AppDefinitionV1) (string, error) { // Pack the data using ABI encoding packed, err := args.Pack( - definition.Application, + definition.ApplicationID, participants, definition.Quorum, definition.Nonce, diff --git a/pkg/app/app_session_v1_test.go b/pkg/app/app_session_v1_test.go index d1bc2b0cd..a5aca19d1 100644 --- a/pkg/app/app_session_v1_test.go +++ b/pkg/app/app_session_v1_test.go @@ -11,7 +11,7 @@ import ( func TestPackCreateAppSessionRequestV1(t *testing.T) { t.Parallel() def := AppDefinitionV1{ - Application: "chess-v1", + ApplicationID: "chess-v1", Participants: []AppParticipantV1{ {WalletAddress: "0x1111111111111111111111111111111111111111", SignatureWeight: 1}, {WalletAddress: "0x2222222222222222222222222222222222222222", SignatureWeight: 1}, @@ -48,7 +48,7 @@ func TestPackAppStateUpdateV1(t *testing.T) { func TestGenerateAppSessionIDV1(t *testing.T) { t.Parallel() def := AppDefinitionV1{ - Application: "chess-v1", + ApplicationID: "chess-v1", Participants: []AppParticipantV1{ {WalletAddress: "0x1111111111111111111111111111111111111111", SignatureWeight: 1}, }, diff --git a/pkg/app/app_v1.go b/pkg/app/app_v1.go new file mode 100644 index 000000000..08ce87afd --- /dev/null +++ b/pkg/app/app_v1.go @@ -0,0 +1,59 @@ +package app + +import ( + "fmt" + "regexp" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +var AppIDV1Regex = regexp.MustCompile(`^[a-z0-9][-a-z0-9]{0,65}$`) + +// AppV1 represents an application registry entry. +type AppV1 struct { + ID string + OwnerWallet string + Metadata string + Version uint64 + CreationApprovalNotRequired bool +} + +// AppInfoV1 represents full application info including timestamps. +type AppInfoV1 struct { + App AppV1 + CreatedAt time.Time + UpdatedAt time.Time +} + +// PackAppV1 packs the AppV1 for signing using ABI encoding. +func PackAppV1(app AppV1) ([]byte, error) { + if !common.IsHexAddress(app.OwnerWallet) { + return nil, fmt.Errorf("invalid owner wallet address: %s", app.OwnerWallet) + } + + args := abi.Arguments{ + {Type: abi.Type{T: abi.StringTy}}, // id + {Type: abi.Type{T: abi.AddressTy}}, // ownerWallet + {Type: abi.Type{T: abi.FixedBytesTy, Size: 32}}, // metadata (bytes32) + {Type: abi.Type{T: abi.UintTy, Size: 64}}, // version + {Type: abi.Type{T: abi.BoolTy}}, // creationApprovalNotRequired + } + + appMetadataHash := crypto.Keccak256Hash([]byte(app.Metadata)) + + packed, err := args.Pack( + app.ID, + common.HexToAddress(app.OwnerWallet), + appMetadataHash, + app.Version, + app.CreationApprovalNotRequired, + ) + if err != nil { + return nil, fmt.Errorf("failed to pack app: %w", err) + } + + return crypto.Keccak256(packed), nil +} diff --git a/pkg/app/session_key_v1.go b/pkg/app/session_key_v1.go index 1d6ef2f20..15db89adf 100644 --- a/pkg/app/session_key_v1.go +++ b/pkg/app/session_key_v1.go @@ -5,10 +5,10 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" ) // AppSessionKeyStateV1 represents the state of a session key. diff --git a/pkg/app/session_key_v1_test.go b/pkg/app/session_key_v1_test.go index 070dba55d..b01748b24 100644 --- a/pkg/app/session_key_v1_test.go +++ b/pkg/app/session_key_v1_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/blockchain/evm/app_registry_abi.go b/pkg/blockchain/evm/app_registry_abi.go new file mode 100644 index 000000000..686285cf7 --- /dev/null +++ b/pkg/blockchain/evm/app_registry_abi.go @@ -0,0 +1,2154 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package evm + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// AppRegistryMetaData contains all meta data concerning the AppRegistry contract. +var AppRegistryMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"asset_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unlockPeriod_\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"admin_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"ADJUDICATOR_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ASSET\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"DEFAULT_ADMIN_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"UNLOCK_PERIOD\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"asset\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleAdmin\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"grantRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"hasRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"lastSlashTimestamp\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"lock\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"lockStateOf\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"enumILock.LockState\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"relock\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"callerConfirmation\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revokeRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashCooldown\",\"inputs\":[{\"name\":\"newCooldown\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slash\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"recipient\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decision\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slashCooldown\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unlock\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unlockTimestampOf\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdraw\",\"inputs\":[{\"name\":\"destination\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Locked\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"deposited\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newBalance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Relocked\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"balance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleAdminChanged\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleGranted\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleRevoked\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashCooldownUpdated\",\"inputs\":[{\"name\":\"oldCooldown\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newCooldown\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Slashed\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"recipient\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"decision\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UnlockInitiated\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"balance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"availableAt\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Withdrawn\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"balance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessControlBadConfirmation\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AccessControlUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"neededRole\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"AlreadyUnlocking\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientBalance\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAddress\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidAmount\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidPeriod\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotLocked\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotUnlocking\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RecipientIsAdjudicator\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"SlashCooldownActive\",\"inputs\":[{\"name\":\"availableAt\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnlockPeriodNotElapsed\",\"inputs\":[{\"name\":\"availableAt\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", + Bin: "0x60c03461010d57601f61176038819003918201601f19168301916001600160401b038311848410176101115780849260609460405283398101031261010d5761004781610125565b90610059604060208301519201610125565b60017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055916001600160a01b038116156100ef5781156100fe5760805260a0526001600160a01b038116156100ef576100b190610139565b5060405161157d90816101c3823960805181818161060d0152818161084c01528181610edd01526110e1015260a05181818161030b0152610aca0152f35b63e6c4247b60e01b5f5260045ffd5b6302e8f35960e31b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b038216820361010d57565b6001600160a01b0381165f9081525f5160206117405f395f51905f52602052604090205460ff166101bd576001600160a01b03165f8181525f5160206117405f395f51905f5260205260408120805460ff191660011790553391907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4600190565b505f9056fe60806040526004361015610011575f80fd5b5f3560e01c8063010379b214610d3257806301ffc9a714610c7357806302f70fe614610c10578063248a9ca314610bbf578063256f8f0914610aed578063259a28cf14610a95578063282d3fdf146107ad5780632f2ff15d1461075157806336568abe146106c957806338d52e0f146106c45780634800d97f146106c457806351cff8d91461058a5780635a6ca4ae1461054f57806370a08231146104ed57806391d1485414610478578063a217fddf14610440578063a43e9cf4146103c1578063a69df4b5146102b5578063c53b573d14610205578063c77f5062146101ca578063d547741f146101675763f538f7dd1461010b575f80fd5b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101635760206040517f5ad6c1ce52091d5f5a49dff6df7d4bf577735e77d8c13251e1ea9c82ce8c380d8152f35b5f80fd5b346101635760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576101c86004356101a4611074565b906101c36101be825f526002602052600160405f20015490565b611201565b61147d565b005b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576020600454604051908152f35b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357335f52600160205260405f20541561028d57335f525f60205260405f2054335f5260016020525f60408120556040519081527fe2d155d9d74decc198a9a9b4f5bddb24ee0842ee745c9ce57e3573b971e50d9d60203392a2005b7ff61050dc000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357335f525f60205260405f2054801561039957335f52600160205260405f2054610371576103307f000000000000000000000000000000000000000000000000000000000000000042611105565b335f5260016020528060405f205560405191825260208201527fd73fc4aafa5101b91e88b8c2fa75d8d6db73466773addb11c079d063c1e63c0c60403392a2005b7fe47b668b000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1834e265000000000000000000000000000000000000000000000000000000005f5260045ffd5b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576104006103fb611051565b6111ba565b6040516003821015610413576020918152f35b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101635760206040515f8152f35b346101635760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576104af611074565b6004355f52600260205273ffffffffffffffffffffffffffffffffffffffff60405f2091165f52602052602060ff60405f2054166040519015158152f35b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101635773ffffffffffffffffffffffffffffffffffffffff610539611051565b165f525f602052602060405f2054604051908152f35b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576020600354604051908152f35b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576105c1611051565b6105c9611268565b335f52600160205260405f2054801561028d578042106106995750335f908152602081815260408083208054908490556001909252822091909155906106479082907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166112df565b6040519081527f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d560203392a260017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055005b7fc33c45d1000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b611097565b346101635760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357610700611074565b3373ffffffffffffffffffffffffffffffffffffffff821603610729576101c89060043561147d565b7f6697b232000000000000000000000000000000000000000000000000000000005f5260045ffd5b346101635760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576101c860043561078e611074565b906107a86101be825f526002602052600160405f20015490565b6113a9565b346101635760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576107e4611051565b602435906107f0611268565b8115610a6d5773ffffffffffffffffffffffffffffffffffffffff1690815f52600160205260405f2054610371576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16602082602481845afa9182156109e4575f92610a39575b50604051927f23b872dd000000000000000000000000000000000000000000000000000000005f52336004523060245260445260205f60648180855af160015f5114811615610a1a575b836040525f606052156109ef57826024816020937f70a082310000000000000000000000000000000000000000000000000000000082523060048301525afa9182156109e4575f926109ae575b5061095d6040917fd4665e3049283582ba6f9eba07a5b3e12dab49e02da99e8927a47af5d134bea59361113f565b835f525f60205261097181835f2054611105565b845f525f60205280835f205582519182526020820152a260017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055005b91506020823d6020116109dc575b816109c96020938361114c565b810103126101635790519061095d61092f565b3d91506109bc565b6040513d5f823e3d90fd5b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6001811516610a3057813b15153d1516166108e2565b833d5f823e3d90fd5b9091506020813d602011610a65575b81610a556020938361114c565b8101031261016357519084610898565b3d9150610a48565b7f2c5211c6000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101635760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357335f9081527fac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b60205260409020546004359060ff1615610b8f5760407f21dd401bad58f48f88b208aad5157305ac7e8ec5db030042aaec08ebd7f50e4c91600354908060035582519182526020820152a1005b7fe2517d3f000000000000000000000000000000000000000000000000000000005f52336004525f60245260445ffd5b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576020610c086004355f526002602052600160405f20015490565b604051908152f35b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101635773ffffffffffffffffffffffffffffffffffffffff610c5c611051565b165f526001602052602060405f2054604051908152f35b346101635760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610163576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361016357807f7965db0b0000000000000000000000000000000000000000000000000000000060209214908115610d08575b506040519015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501482610cfd565b346101635760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357610d69611051565b60243560443573ffffffffffffffffffffffffffffffffffffffff811692838203610163576064359067ffffffffffffffff821161016357366023830112156101635781600401359067ffffffffffffffff821161016357366024838501011161016357335f9081527f7f9272e0d81bdb9b1e0d5b8b11f4dfdbe7d5ebf3410318d28f3e6024ce33d903602052604090205460ff161561100157610e0b611268565b60045460035480151580610ff8575b610fb7575b5050338614610f8f5773ffffffffffffffffffffffffffffffffffffffff1693845f525f60205260405f2054938415610f6757848211610f6757601f606093610f02847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe094610eaf827ff204f6a8b6d1439051d5fbd742389d0f778d18d21016e81c8ad3d4558266454c9b61113f565b8b5f525f6020528060405f205515610f54575b4260045573ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166112df565b80602460405197889687526040602088015282604088015201868601375f85828601015201168101030190a360017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055005b8a5f5260016020525f6040812055610ec2565b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8d3fe42e000000000000000000000000000000000000000000000000000000005f5260045ffd5b610fc091611105565b804210610fcd5780610e1f565b7f773faedc000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b50811515610e1a565b7fe2517d3f000000000000000000000000000000000000000000000000000000005f52336004527f5ad6c1ce52091d5f5a49dff6df7d4bf577735e77d8c13251e1ea9c82ce8c380d60245260445ffd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361016357565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361016357565b34610163575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261016357602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b9190820180921161111257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b9190820391821161111257565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761118d57604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff16805f525f60205260405f2054156111fc575f52600160205260405f2054156111f757600290565b600190565b505f90565b805f52600260205260405f2073ffffffffffffffffffffffffffffffffffffffff33165f5260205260ff60405f205416156112395750565b7fe2517d3f000000000000000000000000000000000000000000000000000000005f523360045260245260445ffd5b60027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0054146112b75760027f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f0055565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b9173ffffffffffffffffffffffffffffffffffffffff604051927fa9059cbb000000000000000000000000000000000000000000000000000000005f521660045260245260205f60448180865af19060015f5114821615611388575b604052156113465750565b73ffffffffffffffffffffffffffffffffffffffff907f5274afe7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b9060018115166113a057823b15153d1516169061133b565b503d5f823e3d90fd5b805f52600260205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260ff60405f205416155f1461147757805f52600260205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905573ffffffffffffffffffffffffffffffffffffffff339216907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d5f80a4600190565b50505f90565b805f52600260205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260ff60405f2054165f1461147757805f52600260205260405f2073ffffffffffffffffffffffffffffffffffffffff83165f5260205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00815416905573ffffffffffffffffffffffffffffffffffffffff339216907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b5f80a460019056fea2646970667358221220d76073a01636f84db6fa5e41c0f05a2695c4df18ed5b02a8e189ed7ed6d4da3a64736f6c63430008220033ac33ff75c19e70fe83507db0d683fd3465c996598dc972688b7ace676c89077b", +} + +// AppRegistryABI is the input ABI used to generate the binding from. +// Deprecated: Use AppRegistryMetaData.ABI instead. +var AppRegistryABI = AppRegistryMetaData.ABI + +// AppRegistryBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use AppRegistryMetaData.Bin instead. +var AppRegistryBin = AppRegistryMetaData.Bin + +// DeployAppRegistry deploys a new Ethereum contract, binding an instance of AppRegistry to it. +func DeployAppRegistry(auth *bind.TransactOpts, backend bind.ContractBackend, asset_ common.Address, unlockPeriod_ *big.Int, admin_ common.Address) (common.Address, *types.Transaction, *AppRegistry, error) { + parsed, err := AppRegistryMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(AppRegistryBin), backend, asset_, unlockPeriod_, admin_) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &AppRegistry{AppRegistryCaller: AppRegistryCaller{contract: contract}, AppRegistryTransactor: AppRegistryTransactor{contract: contract}, AppRegistryFilterer: AppRegistryFilterer{contract: contract}}, nil +} + +// AppRegistry is an auto generated Go binding around an Ethereum contract. +type AppRegistry struct { + AppRegistryCaller // Read-only binding to the contract + AppRegistryTransactor // Write-only binding to the contract + AppRegistryFilterer // Log filterer for contract events +} + +// AppRegistryCaller is an auto generated read-only Go binding around an Ethereum contract. +type AppRegistryCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AppRegistryTransactor is an auto generated write-only Go binding around an Ethereum contract. +type AppRegistryTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AppRegistryFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type AppRegistryFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// AppRegistrySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type AppRegistrySession struct { + Contract *AppRegistry // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AppRegistryCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type AppRegistryCallerSession struct { + Contract *AppRegistryCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// AppRegistryTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type AppRegistryTransactorSession struct { + Contract *AppRegistryTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// AppRegistryRaw is an auto generated low-level Go binding around an Ethereum contract. +type AppRegistryRaw struct { + Contract *AppRegistry // Generic contract binding to access the raw methods on +} + +// AppRegistryCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type AppRegistryCallerRaw struct { + Contract *AppRegistryCaller // Generic read-only contract binding to access the raw methods on +} + +// AppRegistryTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type AppRegistryTransactorRaw struct { + Contract *AppRegistryTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewAppRegistry creates a new instance of AppRegistry, bound to a specific deployed contract. +func NewAppRegistry(address common.Address, backend bind.ContractBackend) (*AppRegistry, error) { + contract, err := bindAppRegistry(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &AppRegistry{AppRegistryCaller: AppRegistryCaller{contract: contract}, AppRegistryTransactor: AppRegistryTransactor{contract: contract}, AppRegistryFilterer: AppRegistryFilterer{contract: contract}}, nil +} + +// NewAppRegistryCaller creates a new read-only instance of AppRegistry, bound to a specific deployed contract. +func NewAppRegistryCaller(address common.Address, caller bind.ContractCaller) (*AppRegistryCaller, error) { + contract, err := bindAppRegistry(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &AppRegistryCaller{contract: contract}, nil +} + +// NewAppRegistryTransactor creates a new write-only instance of AppRegistry, bound to a specific deployed contract. +func NewAppRegistryTransactor(address common.Address, transactor bind.ContractTransactor) (*AppRegistryTransactor, error) { + contract, err := bindAppRegistry(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &AppRegistryTransactor{contract: contract}, nil +} + +// NewAppRegistryFilterer creates a new log filterer instance of AppRegistry, bound to a specific deployed contract. +func NewAppRegistryFilterer(address common.Address, filterer bind.ContractFilterer) (*AppRegistryFilterer, error) { + contract, err := bindAppRegistry(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &AppRegistryFilterer{contract: contract}, nil +} + +// bindAppRegistry binds a generic wrapper to an already deployed contract. +func bindAppRegistry(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(AppRegistryABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AppRegistry *AppRegistryRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _AppRegistry.Contract.AppRegistryCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AppRegistry *AppRegistryRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AppRegistry.Contract.AppRegistryTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AppRegistry *AppRegistryRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AppRegistry.Contract.AppRegistryTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_AppRegistry *AppRegistryCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _AppRegistry.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_AppRegistry *AppRegistryTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AppRegistry.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_AppRegistry *AppRegistryTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AppRegistry.Contract.contract.Transact(opts, method, params...) +} + +// ADJUDICATORROLE is a free data retrieval call binding the contract method 0xf538f7dd. +// +// Solidity: function ADJUDICATOR_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistryCaller) ADJUDICATORROLE(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "ADJUDICATOR_ROLE") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ADJUDICATORROLE is a free data retrieval call binding the contract method 0xf538f7dd. +// +// Solidity: function ADJUDICATOR_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistrySession) ADJUDICATORROLE() ([32]byte, error) { + return _AppRegistry.Contract.ADJUDICATORROLE(&_AppRegistry.CallOpts) +} + +// ADJUDICATORROLE is a free data retrieval call binding the contract method 0xf538f7dd. +// +// Solidity: function ADJUDICATOR_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistryCallerSession) ADJUDICATORROLE() ([32]byte, error) { + return _AppRegistry.Contract.ADJUDICATORROLE(&_AppRegistry.CallOpts) +} + +// ASSET is a free data retrieval call binding the contract method 0x4800d97f. +// +// Solidity: function ASSET() view returns(address) +func (_AppRegistry *AppRegistryCaller) ASSET(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "ASSET") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ASSET is a free data retrieval call binding the contract method 0x4800d97f. +// +// Solidity: function ASSET() view returns(address) +func (_AppRegistry *AppRegistrySession) ASSET() (common.Address, error) { + return _AppRegistry.Contract.ASSET(&_AppRegistry.CallOpts) +} + +// ASSET is a free data retrieval call binding the contract method 0x4800d97f. +// +// Solidity: function ASSET() view returns(address) +func (_AppRegistry *AppRegistryCallerSession) ASSET() (common.Address, error) { + return _AppRegistry.Contract.ASSET(&_AppRegistry.CallOpts) +} + +// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. +// +// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistryCaller) DEFAULTADMINROLE(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "DEFAULT_ADMIN_ROLE") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. +// +// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistrySession) DEFAULTADMINROLE() ([32]byte, error) { + return _AppRegistry.Contract.DEFAULTADMINROLE(&_AppRegistry.CallOpts) +} + +// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. +// +// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) +func (_AppRegistry *AppRegistryCallerSession) DEFAULTADMINROLE() ([32]byte, error) { + return _AppRegistry.Contract.DEFAULTADMINROLE(&_AppRegistry.CallOpts) +} + +// UNLOCKPERIOD is a free data retrieval call binding the contract method 0x259a28cf. +// +// Solidity: function UNLOCK_PERIOD() view returns(uint256) +func (_AppRegistry *AppRegistryCaller) UNLOCKPERIOD(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "UNLOCK_PERIOD") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// UNLOCKPERIOD is a free data retrieval call binding the contract method 0x259a28cf. +// +// Solidity: function UNLOCK_PERIOD() view returns(uint256) +func (_AppRegistry *AppRegistrySession) UNLOCKPERIOD() (*big.Int, error) { + return _AppRegistry.Contract.UNLOCKPERIOD(&_AppRegistry.CallOpts) +} + +// UNLOCKPERIOD is a free data retrieval call binding the contract method 0x259a28cf. +// +// Solidity: function UNLOCK_PERIOD() view returns(uint256) +func (_AppRegistry *AppRegistryCallerSession) UNLOCKPERIOD() (*big.Int, error) { + return _AppRegistry.Contract.UNLOCKPERIOD(&_AppRegistry.CallOpts) +} + +// Asset is a free data retrieval call binding the contract method 0x38d52e0f. +// +// Solidity: function asset() view returns(address) +func (_AppRegistry *AppRegistryCaller) Asset(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "asset") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Asset is a free data retrieval call binding the contract method 0x38d52e0f. +// +// Solidity: function asset() view returns(address) +func (_AppRegistry *AppRegistrySession) Asset() (common.Address, error) { + return _AppRegistry.Contract.Asset(&_AppRegistry.CallOpts) +} + +// Asset is a free data retrieval call binding the contract method 0x38d52e0f. +// +// Solidity: function asset() view returns(address) +func (_AppRegistry *AppRegistryCallerSession) Asset() (common.Address, error) { + return _AppRegistry.Contract.Asset(&_AppRegistry.CallOpts) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistryCaller) BalanceOf(opts *bind.CallOpts, user common.Address) (*big.Int, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "balanceOf", user) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistrySession) BalanceOf(user common.Address) (*big.Int, error) { + return _AppRegistry.Contract.BalanceOf(&_AppRegistry.CallOpts, user) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistryCallerSession) BalanceOf(user common.Address) (*big.Int, error) { + return _AppRegistry.Contract.BalanceOf(&_AppRegistry.CallOpts, user) +} + +// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. +// +// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) +func (_AppRegistry *AppRegistryCaller) GetRoleAdmin(opts *bind.CallOpts, role [32]byte) ([32]byte, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "getRoleAdmin", role) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. +// +// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) +func (_AppRegistry *AppRegistrySession) GetRoleAdmin(role [32]byte) ([32]byte, error) { + return _AppRegistry.Contract.GetRoleAdmin(&_AppRegistry.CallOpts, role) +} + +// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. +// +// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) +func (_AppRegistry *AppRegistryCallerSession) GetRoleAdmin(role [32]byte) ([32]byte, error) { + return _AppRegistry.Contract.GetRoleAdmin(&_AppRegistry.CallOpts, role) +} + +// HasRole is a free data retrieval call binding the contract method 0x91d14854. +// +// Solidity: function hasRole(bytes32 role, address account) view returns(bool) +func (_AppRegistry *AppRegistryCaller) HasRole(opts *bind.CallOpts, role [32]byte, account common.Address) (bool, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "hasRole", role, account) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// HasRole is a free data retrieval call binding the contract method 0x91d14854. +// +// Solidity: function hasRole(bytes32 role, address account) view returns(bool) +func (_AppRegistry *AppRegistrySession) HasRole(role [32]byte, account common.Address) (bool, error) { + return _AppRegistry.Contract.HasRole(&_AppRegistry.CallOpts, role, account) +} + +// HasRole is a free data retrieval call binding the contract method 0x91d14854. +// +// Solidity: function hasRole(bytes32 role, address account) view returns(bool) +func (_AppRegistry *AppRegistryCallerSession) HasRole(role [32]byte, account common.Address) (bool, error) { + return _AppRegistry.Contract.HasRole(&_AppRegistry.CallOpts, role, account) +} + +// LastSlashTimestamp is a free data retrieval call binding the contract method 0xc77f5062. +// +// Solidity: function lastSlashTimestamp() view returns(uint256) +func (_AppRegistry *AppRegistryCaller) LastSlashTimestamp(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "lastSlashTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// LastSlashTimestamp is a free data retrieval call binding the contract method 0xc77f5062. +// +// Solidity: function lastSlashTimestamp() view returns(uint256) +func (_AppRegistry *AppRegistrySession) LastSlashTimestamp() (*big.Int, error) { + return _AppRegistry.Contract.LastSlashTimestamp(&_AppRegistry.CallOpts) +} + +// LastSlashTimestamp is a free data retrieval call binding the contract method 0xc77f5062. +// +// Solidity: function lastSlashTimestamp() view returns(uint256) +func (_AppRegistry *AppRegistryCallerSession) LastSlashTimestamp() (*big.Int, error) { + return _AppRegistry.Contract.LastSlashTimestamp(&_AppRegistry.CallOpts) +} + +// LockStateOf is a free data retrieval call binding the contract method 0xa43e9cf4. +// +// Solidity: function lockStateOf(address user) view returns(uint8) +func (_AppRegistry *AppRegistryCaller) LockStateOf(opts *bind.CallOpts, user common.Address) (uint8, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "lockStateOf", user) + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// LockStateOf is a free data retrieval call binding the contract method 0xa43e9cf4. +// +// Solidity: function lockStateOf(address user) view returns(uint8) +func (_AppRegistry *AppRegistrySession) LockStateOf(user common.Address) (uint8, error) { + return _AppRegistry.Contract.LockStateOf(&_AppRegistry.CallOpts, user) +} + +// LockStateOf is a free data retrieval call binding the contract method 0xa43e9cf4. +// +// Solidity: function lockStateOf(address user) view returns(uint8) +func (_AppRegistry *AppRegistryCallerSession) LockStateOf(user common.Address) (uint8, error) { + return _AppRegistry.Contract.LockStateOf(&_AppRegistry.CallOpts, user) +} + +// SlashCooldown is a free data retrieval call binding the contract method 0x5a6ca4ae. +// +// Solidity: function slashCooldown() view returns(uint256) +func (_AppRegistry *AppRegistryCaller) SlashCooldown(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "slashCooldown") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// SlashCooldown is a free data retrieval call binding the contract method 0x5a6ca4ae. +// +// Solidity: function slashCooldown() view returns(uint256) +func (_AppRegistry *AppRegistrySession) SlashCooldown() (*big.Int, error) { + return _AppRegistry.Contract.SlashCooldown(&_AppRegistry.CallOpts) +} + +// SlashCooldown is a free data retrieval call binding the contract method 0x5a6ca4ae. +// +// Solidity: function slashCooldown() view returns(uint256) +func (_AppRegistry *AppRegistryCallerSession) SlashCooldown() (*big.Int, error) { + return _AppRegistry.Contract.SlashCooldown(&_AppRegistry.CallOpts) +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_AppRegistry *AppRegistryCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "supportsInterface", interfaceId) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_AppRegistry *AppRegistrySession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _AppRegistry.Contract.SupportsInterface(&_AppRegistry.CallOpts, interfaceId) +} + +// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. +// +// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) +func (_AppRegistry *AppRegistryCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { + return _AppRegistry.Contract.SupportsInterface(&_AppRegistry.CallOpts, interfaceId) +} + +// UnlockTimestampOf is a free data retrieval call binding the contract method 0x02f70fe6. +// +// Solidity: function unlockTimestampOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistryCaller) UnlockTimestampOf(opts *bind.CallOpts, user common.Address) (*big.Int, error) { + var out []interface{} + err := _AppRegistry.contract.Call(opts, &out, "unlockTimestampOf", user) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// UnlockTimestampOf is a free data retrieval call binding the contract method 0x02f70fe6. +// +// Solidity: function unlockTimestampOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistrySession) UnlockTimestampOf(user common.Address) (*big.Int, error) { + return _AppRegistry.Contract.UnlockTimestampOf(&_AppRegistry.CallOpts, user) +} + +// UnlockTimestampOf is a free data retrieval call binding the contract method 0x02f70fe6. +// +// Solidity: function unlockTimestampOf(address user) view returns(uint256) +func (_AppRegistry *AppRegistryCallerSession) UnlockTimestampOf(user common.Address) (*big.Int, error) { + return _AppRegistry.Contract.UnlockTimestampOf(&_AppRegistry.CallOpts, user) +} + +// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. +// +// Solidity: function grantRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistryTransactor) GrantRole(opts *bind.TransactOpts, role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "grantRole", role, account) +} + +// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. +// +// Solidity: function grantRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistrySession) GrantRole(role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.GrantRole(&_AppRegistry.TransactOpts, role, account) +} + +// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. +// +// Solidity: function grantRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistryTransactorSession) GrantRole(role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.GrantRole(&_AppRegistry.TransactOpts, role, account) +} + +// Lock is a paid mutator transaction binding the contract method 0x282d3fdf. +// +// Solidity: function lock(address target, uint256 amount) returns() +func (_AppRegistry *AppRegistryTransactor) Lock(opts *bind.TransactOpts, target common.Address, amount *big.Int) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "lock", target, amount) +} + +// Lock is a paid mutator transaction binding the contract method 0x282d3fdf. +// +// Solidity: function lock(address target, uint256 amount) returns() +func (_AppRegistry *AppRegistrySession) Lock(target common.Address, amount *big.Int) (*types.Transaction, error) { + return _AppRegistry.Contract.Lock(&_AppRegistry.TransactOpts, target, amount) +} + +// Lock is a paid mutator transaction binding the contract method 0x282d3fdf. +// +// Solidity: function lock(address target, uint256 amount) returns() +func (_AppRegistry *AppRegistryTransactorSession) Lock(target common.Address, amount *big.Int) (*types.Transaction, error) { + return _AppRegistry.Contract.Lock(&_AppRegistry.TransactOpts, target, amount) +} + +// Relock is a paid mutator transaction binding the contract method 0xc53b573d. +// +// Solidity: function relock() returns() +func (_AppRegistry *AppRegistryTransactor) Relock(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "relock") +} + +// Relock is a paid mutator transaction binding the contract method 0xc53b573d. +// +// Solidity: function relock() returns() +func (_AppRegistry *AppRegistrySession) Relock() (*types.Transaction, error) { + return _AppRegistry.Contract.Relock(&_AppRegistry.TransactOpts) +} + +// Relock is a paid mutator transaction binding the contract method 0xc53b573d. +// +// Solidity: function relock() returns() +func (_AppRegistry *AppRegistryTransactorSession) Relock() (*types.Transaction, error) { + return _AppRegistry.Contract.Relock(&_AppRegistry.TransactOpts) +} + +// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. +// +// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() +func (_AppRegistry *AppRegistryTransactor) RenounceRole(opts *bind.TransactOpts, role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "renounceRole", role, callerConfirmation) +} + +// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. +// +// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() +func (_AppRegistry *AppRegistrySession) RenounceRole(role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.RenounceRole(&_AppRegistry.TransactOpts, role, callerConfirmation) +} + +// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. +// +// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() +func (_AppRegistry *AppRegistryTransactorSession) RenounceRole(role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.RenounceRole(&_AppRegistry.TransactOpts, role, callerConfirmation) +} + +// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. +// +// Solidity: function revokeRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistryTransactor) RevokeRole(opts *bind.TransactOpts, role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "revokeRole", role, account) +} + +// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. +// +// Solidity: function revokeRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistrySession) RevokeRole(role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.RevokeRole(&_AppRegistry.TransactOpts, role, account) +} + +// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. +// +// Solidity: function revokeRole(bytes32 role, address account) returns() +func (_AppRegistry *AppRegistryTransactorSession) RevokeRole(role [32]byte, account common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.RevokeRole(&_AppRegistry.TransactOpts, role, account) +} + +// SetSlashCooldown is a paid mutator transaction binding the contract method 0x256f8f09. +// +// Solidity: function setSlashCooldown(uint256 newCooldown) returns() +func (_AppRegistry *AppRegistryTransactor) SetSlashCooldown(opts *bind.TransactOpts, newCooldown *big.Int) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "setSlashCooldown", newCooldown) +} + +// SetSlashCooldown is a paid mutator transaction binding the contract method 0x256f8f09. +// +// Solidity: function setSlashCooldown(uint256 newCooldown) returns() +func (_AppRegistry *AppRegistrySession) SetSlashCooldown(newCooldown *big.Int) (*types.Transaction, error) { + return _AppRegistry.Contract.SetSlashCooldown(&_AppRegistry.TransactOpts, newCooldown) +} + +// SetSlashCooldown is a paid mutator transaction binding the contract method 0x256f8f09. +// +// Solidity: function setSlashCooldown(uint256 newCooldown) returns() +func (_AppRegistry *AppRegistryTransactorSession) SetSlashCooldown(newCooldown *big.Int) (*types.Transaction, error) { + return _AppRegistry.Contract.SetSlashCooldown(&_AppRegistry.TransactOpts, newCooldown) +} + +// Slash is a paid mutator transaction binding the contract method 0x010379b2. +// +// Solidity: function slash(address user, uint256 amount, address recipient, bytes decision) returns() +func (_AppRegistry *AppRegistryTransactor) Slash(opts *bind.TransactOpts, user common.Address, amount *big.Int, recipient common.Address, decision []byte) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "slash", user, amount, recipient, decision) +} + +// Slash is a paid mutator transaction binding the contract method 0x010379b2. +// +// Solidity: function slash(address user, uint256 amount, address recipient, bytes decision) returns() +func (_AppRegistry *AppRegistrySession) Slash(user common.Address, amount *big.Int, recipient common.Address, decision []byte) (*types.Transaction, error) { + return _AppRegistry.Contract.Slash(&_AppRegistry.TransactOpts, user, amount, recipient, decision) +} + +// Slash is a paid mutator transaction binding the contract method 0x010379b2. +// +// Solidity: function slash(address user, uint256 amount, address recipient, bytes decision) returns() +func (_AppRegistry *AppRegistryTransactorSession) Slash(user common.Address, amount *big.Int, recipient common.Address, decision []byte) (*types.Transaction, error) { + return _AppRegistry.Contract.Slash(&_AppRegistry.TransactOpts, user, amount, recipient, decision) +} + +// Unlock is a paid mutator transaction binding the contract method 0xa69df4b5. +// +// Solidity: function unlock() returns() +func (_AppRegistry *AppRegistryTransactor) Unlock(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "unlock") +} + +// Unlock is a paid mutator transaction binding the contract method 0xa69df4b5. +// +// Solidity: function unlock() returns() +func (_AppRegistry *AppRegistrySession) Unlock() (*types.Transaction, error) { + return _AppRegistry.Contract.Unlock(&_AppRegistry.TransactOpts) +} + +// Unlock is a paid mutator transaction binding the contract method 0xa69df4b5. +// +// Solidity: function unlock() returns() +func (_AppRegistry *AppRegistryTransactorSession) Unlock() (*types.Transaction, error) { + return _AppRegistry.Contract.Unlock(&_AppRegistry.TransactOpts) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x51cff8d9. +// +// Solidity: function withdraw(address destination) returns() +func (_AppRegistry *AppRegistryTransactor) Withdraw(opts *bind.TransactOpts, destination common.Address) (*types.Transaction, error) { + return _AppRegistry.contract.Transact(opts, "withdraw", destination) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x51cff8d9. +// +// Solidity: function withdraw(address destination) returns() +func (_AppRegistry *AppRegistrySession) Withdraw(destination common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.Withdraw(&_AppRegistry.TransactOpts, destination) +} + +// Withdraw is a paid mutator transaction binding the contract method 0x51cff8d9. +// +// Solidity: function withdraw(address destination) returns() +func (_AppRegistry *AppRegistryTransactorSession) Withdraw(destination common.Address) (*types.Transaction, error) { + return _AppRegistry.Contract.Withdraw(&_AppRegistry.TransactOpts, destination) +} + +// AppRegistryLockedIterator is returned from FilterLocked and is used to iterate over the raw logs and unpacked data for Locked events raised by the AppRegistry contract. +type AppRegistryLockedIterator struct { + Event *AppRegistryLocked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryLockedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryLocked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryLocked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryLockedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryLockedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryLocked represents a Locked event raised by the AppRegistry contract. +type AppRegistryLocked struct { + User common.Address + Deposited *big.Int + NewBalance *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterLocked is a free log retrieval operation binding the contract event 0xd4665e3049283582ba6f9eba07a5b3e12dab49e02da99e8927a47af5d134bea5. +// +// Solidity: event Locked(address indexed user, uint256 deposited, uint256 newBalance) +func (_AppRegistry *AppRegistryFilterer) FilterLocked(opts *bind.FilterOpts, user []common.Address) (*AppRegistryLockedIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "Locked", userRule) + if err != nil { + return nil, err + } + return &AppRegistryLockedIterator{contract: _AppRegistry.contract, event: "Locked", logs: logs, sub: sub}, nil +} + +// WatchLocked is a free log subscription operation binding the contract event 0xd4665e3049283582ba6f9eba07a5b3e12dab49e02da99e8927a47af5d134bea5. +// +// Solidity: event Locked(address indexed user, uint256 deposited, uint256 newBalance) +func (_AppRegistry *AppRegistryFilterer) WatchLocked(opts *bind.WatchOpts, sink chan<- *AppRegistryLocked, user []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "Locked", userRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryLocked) + if err := _AppRegistry.contract.UnpackLog(event, "Locked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseLocked is a log parse operation binding the contract event 0xd4665e3049283582ba6f9eba07a5b3e12dab49e02da99e8927a47af5d134bea5. +// +// Solidity: event Locked(address indexed user, uint256 deposited, uint256 newBalance) +func (_AppRegistry *AppRegistryFilterer) ParseLocked(log types.Log) (*AppRegistryLocked, error) { + event := new(AppRegistryLocked) + if err := _AppRegistry.contract.UnpackLog(event, "Locked", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryRelockedIterator is returned from FilterRelocked and is used to iterate over the raw logs and unpacked data for Relocked events raised by the AppRegistry contract. +type AppRegistryRelockedIterator struct { + Event *AppRegistryRelocked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryRelockedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryRelocked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryRelocked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryRelockedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryRelockedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryRelocked represents a Relocked event raised by the AppRegistry contract. +type AppRegistryRelocked struct { + User common.Address + Balance *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRelocked is a free log retrieval operation binding the contract event 0xe2d155d9d74decc198a9a9b4f5bddb24ee0842ee745c9ce57e3573b971e50d9d. +// +// Solidity: event Relocked(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) FilterRelocked(opts *bind.FilterOpts, user []common.Address) (*AppRegistryRelockedIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "Relocked", userRule) + if err != nil { + return nil, err + } + return &AppRegistryRelockedIterator{contract: _AppRegistry.contract, event: "Relocked", logs: logs, sub: sub}, nil +} + +// WatchRelocked is a free log subscription operation binding the contract event 0xe2d155d9d74decc198a9a9b4f5bddb24ee0842ee745c9ce57e3573b971e50d9d. +// +// Solidity: event Relocked(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) WatchRelocked(opts *bind.WatchOpts, sink chan<- *AppRegistryRelocked, user []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "Relocked", userRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryRelocked) + if err := _AppRegistry.contract.UnpackLog(event, "Relocked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRelocked is a log parse operation binding the contract event 0xe2d155d9d74decc198a9a9b4f5bddb24ee0842ee745c9ce57e3573b971e50d9d. +// +// Solidity: event Relocked(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) ParseRelocked(log types.Log) (*AppRegistryRelocked, error) { + event := new(AppRegistryRelocked) + if err := _AppRegistry.contract.UnpackLog(event, "Relocked", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryRoleAdminChangedIterator is returned from FilterRoleAdminChanged and is used to iterate over the raw logs and unpacked data for RoleAdminChanged events raised by the AppRegistry contract. +type AppRegistryRoleAdminChangedIterator struct { + Event *AppRegistryRoleAdminChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryRoleAdminChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryRoleAdminChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryRoleAdminChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryRoleAdminChanged represents a RoleAdminChanged event raised by the AppRegistry contract. +type AppRegistryRoleAdminChanged struct { + Role [32]byte + PreviousAdminRole [32]byte + NewAdminRole [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleAdminChanged is a free log retrieval operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. +// +// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) +func (_AppRegistry *AppRegistryFilterer) FilterRoleAdminChanged(opts *bind.FilterOpts, role [][32]byte, previousAdminRole [][32]byte, newAdminRole [][32]byte) (*AppRegistryRoleAdminChangedIterator, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var previousAdminRoleRule []interface{} + for _, previousAdminRoleItem := range previousAdminRole { + previousAdminRoleRule = append(previousAdminRoleRule, previousAdminRoleItem) + } + var newAdminRoleRule []interface{} + for _, newAdminRoleItem := range newAdminRole { + newAdminRoleRule = append(newAdminRoleRule, newAdminRoleItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "RoleAdminChanged", roleRule, previousAdminRoleRule, newAdminRoleRule) + if err != nil { + return nil, err + } + return &AppRegistryRoleAdminChangedIterator{contract: _AppRegistry.contract, event: "RoleAdminChanged", logs: logs, sub: sub}, nil +} + +// WatchRoleAdminChanged is a free log subscription operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. +// +// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) +func (_AppRegistry *AppRegistryFilterer) WatchRoleAdminChanged(opts *bind.WatchOpts, sink chan<- *AppRegistryRoleAdminChanged, role [][32]byte, previousAdminRole [][32]byte, newAdminRole [][32]byte) (event.Subscription, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var previousAdminRoleRule []interface{} + for _, previousAdminRoleItem := range previousAdminRole { + previousAdminRoleRule = append(previousAdminRoleRule, previousAdminRoleItem) + } + var newAdminRoleRule []interface{} + for _, newAdminRoleItem := range newAdminRole { + newAdminRoleRule = append(newAdminRoleRule, newAdminRoleItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "RoleAdminChanged", roleRule, previousAdminRoleRule, newAdminRoleRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryRoleAdminChanged) + if err := _AppRegistry.contract.UnpackLog(event, "RoleAdminChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleAdminChanged is a log parse operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. +// +// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) +func (_AppRegistry *AppRegistryFilterer) ParseRoleAdminChanged(log types.Log) (*AppRegistryRoleAdminChanged, error) { + event := new(AppRegistryRoleAdminChanged) + if err := _AppRegistry.contract.UnpackLog(event, "RoleAdminChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryRoleGrantedIterator is returned from FilterRoleGranted and is used to iterate over the raw logs and unpacked data for RoleGranted events raised by the AppRegistry contract. +type AppRegistryRoleGrantedIterator struct { + Event *AppRegistryRoleGranted // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryRoleGrantedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleGranted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleGranted) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryRoleGrantedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryRoleGrantedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryRoleGranted represents a RoleGranted event raised by the AppRegistry contract. +type AppRegistryRoleGranted struct { + Role [32]byte + Account common.Address + Sender common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleGranted is a free log retrieval operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. +// +// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) FilterRoleGranted(opts *bind.FilterOpts, role [][32]byte, account []common.Address, sender []common.Address) (*AppRegistryRoleGrantedIterator, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "RoleGranted", roleRule, accountRule, senderRule) + if err != nil { + return nil, err + } + return &AppRegistryRoleGrantedIterator{contract: _AppRegistry.contract, event: "RoleGranted", logs: logs, sub: sub}, nil +} + +// WatchRoleGranted is a free log subscription operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. +// +// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) WatchRoleGranted(opts *bind.WatchOpts, sink chan<- *AppRegistryRoleGranted, role [][32]byte, account []common.Address, sender []common.Address) (event.Subscription, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "RoleGranted", roleRule, accountRule, senderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryRoleGranted) + if err := _AppRegistry.contract.UnpackLog(event, "RoleGranted", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleGranted is a log parse operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. +// +// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) ParseRoleGranted(log types.Log) (*AppRegistryRoleGranted, error) { + event := new(AppRegistryRoleGranted) + if err := _AppRegistry.contract.UnpackLog(event, "RoleGranted", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryRoleRevokedIterator is returned from FilterRoleRevoked and is used to iterate over the raw logs and unpacked data for RoleRevoked events raised by the AppRegistry contract. +type AppRegistryRoleRevokedIterator struct { + Event *AppRegistryRoleRevoked // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryRoleRevokedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryRoleRevoked) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryRoleRevokedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryRoleRevokedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryRoleRevoked represents a RoleRevoked event raised by the AppRegistry contract. +type AppRegistryRoleRevoked struct { + Role [32]byte + Account common.Address + Sender common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRoleRevoked is a free log retrieval operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. +// +// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) FilterRoleRevoked(opts *bind.FilterOpts, role [][32]byte, account []common.Address, sender []common.Address) (*AppRegistryRoleRevokedIterator, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "RoleRevoked", roleRule, accountRule, senderRule) + if err != nil { + return nil, err + } + return &AppRegistryRoleRevokedIterator{contract: _AppRegistry.contract, event: "RoleRevoked", logs: logs, sub: sub}, nil +} + +// WatchRoleRevoked is a free log subscription operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. +// +// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) WatchRoleRevoked(opts *bind.WatchOpts, sink chan<- *AppRegistryRoleRevoked, role [][32]byte, account []common.Address, sender []common.Address) (event.Subscription, error) { + + var roleRule []interface{} + for _, roleItem := range role { + roleRule = append(roleRule, roleItem) + } + var accountRule []interface{} + for _, accountItem := range account { + accountRule = append(accountRule, accountItem) + } + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "RoleRevoked", roleRule, accountRule, senderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryRoleRevoked) + if err := _AppRegistry.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRoleRevoked is a log parse operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. +// +// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) +func (_AppRegistry *AppRegistryFilterer) ParseRoleRevoked(log types.Log) (*AppRegistryRoleRevoked, error) { + event := new(AppRegistryRoleRevoked) + if err := _AppRegistry.contract.UnpackLog(event, "RoleRevoked", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistrySlashCooldownUpdatedIterator is returned from FilterSlashCooldownUpdated and is used to iterate over the raw logs and unpacked data for SlashCooldownUpdated events raised by the AppRegistry contract. +type AppRegistrySlashCooldownUpdatedIterator struct { + Event *AppRegistrySlashCooldownUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistrySlashCooldownUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistrySlashCooldownUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistrySlashCooldownUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistrySlashCooldownUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistrySlashCooldownUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistrySlashCooldownUpdated represents a SlashCooldownUpdated event raised by the AppRegistry contract. +type AppRegistrySlashCooldownUpdated struct { + OldCooldown *big.Int + NewCooldown *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSlashCooldownUpdated is a free log retrieval operation binding the contract event 0x21dd401bad58f48f88b208aad5157305ac7e8ec5db030042aaec08ebd7f50e4c. +// +// Solidity: event SlashCooldownUpdated(uint256 oldCooldown, uint256 newCooldown) +func (_AppRegistry *AppRegistryFilterer) FilterSlashCooldownUpdated(opts *bind.FilterOpts) (*AppRegistrySlashCooldownUpdatedIterator, error) { + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "SlashCooldownUpdated") + if err != nil { + return nil, err + } + return &AppRegistrySlashCooldownUpdatedIterator{contract: _AppRegistry.contract, event: "SlashCooldownUpdated", logs: logs, sub: sub}, nil +} + +// WatchSlashCooldownUpdated is a free log subscription operation binding the contract event 0x21dd401bad58f48f88b208aad5157305ac7e8ec5db030042aaec08ebd7f50e4c. +// +// Solidity: event SlashCooldownUpdated(uint256 oldCooldown, uint256 newCooldown) +func (_AppRegistry *AppRegistryFilterer) WatchSlashCooldownUpdated(opts *bind.WatchOpts, sink chan<- *AppRegistrySlashCooldownUpdated) (event.Subscription, error) { + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "SlashCooldownUpdated") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistrySlashCooldownUpdated) + if err := _AppRegistry.contract.UnpackLog(event, "SlashCooldownUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSlashCooldownUpdated is a log parse operation binding the contract event 0x21dd401bad58f48f88b208aad5157305ac7e8ec5db030042aaec08ebd7f50e4c. +// +// Solidity: event SlashCooldownUpdated(uint256 oldCooldown, uint256 newCooldown) +func (_AppRegistry *AppRegistryFilterer) ParseSlashCooldownUpdated(log types.Log) (*AppRegistrySlashCooldownUpdated, error) { + event := new(AppRegistrySlashCooldownUpdated) + if err := _AppRegistry.contract.UnpackLog(event, "SlashCooldownUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistrySlashedIterator is returned from FilterSlashed and is used to iterate over the raw logs and unpacked data for Slashed events raised by the AppRegistry contract. +type AppRegistrySlashedIterator struct { + Event *AppRegistrySlashed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistrySlashedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistrySlashed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistrySlashed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistrySlashedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistrySlashedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistrySlashed represents a Slashed event raised by the AppRegistry contract. +type AppRegistrySlashed struct { + User common.Address + Amount *big.Int + Recipient common.Address + Decision []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSlashed is a free log retrieval operation binding the contract event 0xf204f6a8b6d1439051d5fbd742389d0f778d18d21016e81c8ad3d4558266454c. +// +// Solidity: event Slashed(address indexed user, uint256 amount, address indexed recipient, bytes decision) +func (_AppRegistry *AppRegistryFilterer) FilterSlashed(opts *bind.FilterOpts, user []common.Address, recipient []common.Address) (*AppRegistrySlashedIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "Slashed", userRule, recipientRule) + if err != nil { + return nil, err + } + return &AppRegistrySlashedIterator{contract: _AppRegistry.contract, event: "Slashed", logs: logs, sub: sub}, nil +} + +// WatchSlashed is a free log subscription operation binding the contract event 0xf204f6a8b6d1439051d5fbd742389d0f778d18d21016e81c8ad3d4558266454c. +// +// Solidity: event Slashed(address indexed user, uint256 amount, address indexed recipient, bytes decision) +func (_AppRegistry *AppRegistryFilterer) WatchSlashed(opts *bind.WatchOpts, sink chan<- *AppRegistrySlashed, user []common.Address, recipient []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "Slashed", userRule, recipientRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistrySlashed) + if err := _AppRegistry.contract.UnpackLog(event, "Slashed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSlashed is a log parse operation binding the contract event 0xf204f6a8b6d1439051d5fbd742389d0f778d18d21016e81c8ad3d4558266454c. +// +// Solidity: event Slashed(address indexed user, uint256 amount, address indexed recipient, bytes decision) +func (_AppRegistry *AppRegistryFilterer) ParseSlashed(log types.Log) (*AppRegistrySlashed, error) { + event := new(AppRegistrySlashed) + if err := _AppRegistry.contract.UnpackLog(event, "Slashed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryUnlockInitiatedIterator is returned from FilterUnlockInitiated and is used to iterate over the raw logs and unpacked data for UnlockInitiated events raised by the AppRegistry contract. +type AppRegistryUnlockInitiatedIterator struct { + Event *AppRegistryUnlockInitiated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryUnlockInitiatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryUnlockInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryUnlockInitiated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryUnlockInitiatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryUnlockInitiatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryUnlockInitiated represents a UnlockInitiated event raised by the AppRegistry contract. +type AppRegistryUnlockInitiated struct { + User common.Address + Balance *big.Int + AvailableAt *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUnlockInitiated is a free log retrieval operation binding the contract event 0xd73fc4aafa5101b91e88b8c2fa75d8d6db73466773addb11c079d063c1e63c0c. +// +// Solidity: event UnlockInitiated(address indexed user, uint256 balance, uint256 availableAt) +func (_AppRegistry *AppRegistryFilterer) FilterUnlockInitiated(opts *bind.FilterOpts, user []common.Address) (*AppRegistryUnlockInitiatedIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "UnlockInitiated", userRule) + if err != nil { + return nil, err + } + return &AppRegistryUnlockInitiatedIterator{contract: _AppRegistry.contract, event: "UnlockInitiated", logs: logs, sub: sub}, nil +} + +// WatchUnlockInitiated is a free log subscription operation binding the contract event 0xd73fc4aafa5101b91e88b8c2fa75d8d6db73466773addb11c079d063c1e63c0c. +// +// Solidity: event UnlockInitiated(address indexed user, uint256 balance, uint256 availableAt) +func (_AppRegistry *AppRegistryFilterer) WatchUnlockInitiated(opts *bind.WatchOpts, sink chan<- *AppRegistryUnlockInitiated, user []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "UnlockInitiated", userRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryUnlockInitiated) + if err := _AppRegistry.contract.UnpackLog(event, "UnlockInitiated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUnlockInitiated is a log parse operation binding the contract event 0xd73fc4aafa5101b91e88b8c2fa75d8d6db73466773addb11c079d063c1e63c0c. +// +// Solidity: event UnlockInitiated(address indexed user, uint256 balance, uint256 availableAt) +func (_AppRegistry *AppRegistryFilterer) ParseUnlockInitiated(log types.Log) (*AppRegistryUnlockInitiated, error) { + event := new(AppRegistryUnlockInitiated) + if err := _AppRegistry.contract.UnpackLog(event, "UnlockInitiated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// AppRegistryWithdrawnIterator is returned from FilterWithdrawn and is used to iterate over the raw logs and unpacked data for Withdrawn events raised by the AppRegistry contract. +type AppRegistryWithdrawnIterator struct { + Event *AppRegistryWithdrawn // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *AppRegistryWithdrawnIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(AppRegistryWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(AppRegistryWithdrawn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *AppRegistryWithdrawnIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *AppRegistryWithdrawnIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// AppRegistryWithdrawn represents a Withdrawn event raised by the AppRegistry contract. +type AppRegistryWithdrawn struct { + User common.Address + Balance *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterWithdrawn is a free log retrieval operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. +// +// Solidity: event Withdrawn(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) FilterWithdrawn(opts *bind.FilterOpts, user []common.Address) (*AppRegistryWithdrawnIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.FilterLogs(opts, "Withdrawn", userRule) + if err != nil { + return nil, err + } + return &AppRegistryWithdrawnIterator{contract: _AppRegistry.contract, event: "Withdrawn", logs: logs, sub: sub}, nil +} + +// WatchWithdrawn is a free log subscription operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. +// +// Solidity: event Withdrawn(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) WatchWithdrawn(opts *bind.WatchOpts, sink chan<- *AppRegistryWithdrawn, user []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + + logs, sub, err := _AppRegistry.contract.WatchLogs(opts, "Withdrawn", userRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(AppRegistryWithdrawn) + if err := _AppRegistry.contract.UnpackLog(event, "Withdrawn", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseWithdrawn is a log parse operation binding the contract event 0x7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5. +// +// Solidity: event Withdrawn(address indexed user, uint256 balance) +func (_AppRegistry *AppRegistryFilterer) ParseWithdrawn(log types.Log) (*AppRegistryWithdrawn, error) { + event := new(AppRegistryWithdrawn) + if err := _AppRegistry.contract.UnpackLog(event, "Withdrawn", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/pkg/blockchain/evm/base_client.go b/pkg/blockchain/evm/base_client.go new file mode 100644 index 000000000..83bd50442 --- /dev/null +++ b/pkg/blockchain/evm/base_client.go @@ -0,0 +1,7 @@ +package evm + +// BaseClient holds the shared fields for all EVM-based clients. +type BaseClient struct { + evmClient EVMClient + blockchainID uint64 +} diff --git a/pkg/blockchain/evm/client.go b/pkg/blockchain/evm/blockchain_client.go similarity index 80% rename from pkg/blockchain/evm/client.go rename to pkg/blockchain/evm/blockchain_client.go index d1cbc71db..c922c354a 100644 --- a/pkg/blockchain/evm/client.go +++ b/pkg/blockchain/evm/blockchain_client.go @@ -2,7 +2,9 @@ package evm import ( "context" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -10,20 +12,20 @@ import ( "github.com/pkg/errors" "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" ) -var _ core.Client = &Client{} +var _ core.BlockchainClient = &BlockchainClient{} -type Client struct { - contract *ChannelHub - transactOpts *bind.TransactOpts - nodeAddress common.Address - contractAddress common.Address - blockchainID uint64 - assetStore AssetStore - evmClient EVMClient +type BlockchainClient struct { + BaseClient + contract *ChannelHub + transactOpts *bind.TransactOpts + txSigner sign.Signer + nodeAddress common.Address + channelHubContractAddress common.Address + assetStore AssetStore requireCheckAllowance bool requireCheckBalance bool @@ -31,10 +33,10 @@ type Client struct { } type ClientOption interface { - apply(c *Client) + apply(c *BlockchainClient) } -func NewClient( +func NewBlockchainClient( contractAddress common.Address, evmClient EVMClient, txSigner sign.Signer, @@ -42,19 +44,22 @@ func NewClient( nodeAddress string, assetStore AssetStore, opts ...ClientOption, -) (*Client, error) { +) (*BlockchainClient, error) { contract, err := NewChannelHub(contractAddress, evmClient) if err != nil { return nil, errors.Wrap(err, "failed to create ChannelHub contract instance") } - client := &Client{ - contract: contract, - transactOpts: signerTxOpts(txSigner, blockchainID), - nodeAddress: common.HexToAddress(nodeAddress), - contractAddress: contractAddress, - blockchainID: blockchainID, - assetStore: assetStore, - evmClient: evmClient, + client := &BlockchainClient{ + BaseClient: BaseClient{ + evmClient: evmClient, + blockchainID: blockchainID, + }, + contract: contract, + transactOpts: signerTxOpts(txSigner, blockchainID), + txSigner: txSigner, + nodeAddress: common.HexToAddress(nodeAddress), + channelHubContractAddress: contractAddress, + assetStore: assetStore, requireCheckAllowance: true, requireCheckBalance: true, @@ -69,7 +74,7 @@ func NewClient( // ========= Getters - IVault ========= -func (c *Client) GetAccountsBalances(accounts []string, tokens []string) ([][]decimal.Decimal, error) { +func (c *BlockchainClient) GetAccountsBalances(accounts []string, tokens []string) ([][]decimal.Decimal, error) { if len(accounts) == 0 || len(tokens) == 0 { return [][]decimal.Decimal{}, nil } @@ -92,7 +97,7 @@ func (c *Client) GetAccountsBalances(accounts []string, tokens []string) ([][]de return result, nil } -func (c *Client) getAllowance(asset string, owner string) (decimal.Decimal, error) { +func (c *BlockchainClient) getAllowance(asset string, owner string) (decimal.Decimal, error) { tokenAddrHex, err := c.assetStore.GetTokenAddress(asset, c.blockchainID) if err != nil { return decimal.Zero, errors.Wrap(err, "failed to get token address") @@ -109,7 +114,7 @@ func (c *Client) getAllowance(asset string, owner string) (decimal.Decimal, erro if err != nil { return decimal.Zero, errors.Wrap(err, "failed to instantiate token contract") } - allowance, err := erc20Contract.Allowance(&bind.CallOpts{}, ownerAddr, c.contractAddress) + allowance, err := erc20Contract.Allowance(&bind.CallOpts{}, ownerAddr, c.channelHubContractAddress) if err != nil { return decimal.Zero, errors.Wrapf(err, "failed to get allowance for token %s", asset) } @@ -122,7 +127,7 @@ func (c *Client) getAllowance(asset string, owner string) (decimal.Decimal, erro return decimal.NewFromBigInt(allowance, -int32(decimals)), nil } -func (c *Client) GetTokenBalance(asset string, walletAddress string) (decimal.Decimal, error) { +func (c *BlockchainClient) GetTokenBalance(asset string, walletAddress string) (decimal.Decimal, error) { tokenAddrHex, err := c.assetStore.GetTokenAddress(asset, c.blockchainID) if err != nil { return decimal.Zero, errors.Wrap(err, "failed to get token address") @@ -160,7 +165,7 @@ func (c *Client) GetTokenBalance(asset string, walletAddress string) (decimal.De // ========= Getters - ChannelsHub ========= -func (c *Client) GetNodeBalance(token string) (decimal.Decimal, error) { +func (c *BlockchainClient) GetNodeBalance(token string) (decimal.Decimal, error) { tokenAddr := common.HexToAddress(token) balance, err := c.contract.GetAccountBalance(nil, c.nodeAddress, tokenAddr) if err != nil { @@ -173,7 +178,7 @@ func (c *Client) GetNodeBalance(token string) (decimal.Decimal, error) { return decimal.NewFromBigInt(balance, -int32(decimals)), nil } -func (c *Client) GetOpenChannels(user string) ([]string, error) { +func (c *BlockchainClient) GetOpenChannels(user string) ([]string, error) { userAddr := common.HexToAddress(user) channelIDs, err := c.contract.GetOpenChannels(nil, userAddr) if err != nil { @@ -187,7 +192,7 @@ func (c *Client) GetOpenChannels(user string) ([]string, error) { return result, nil } -func (c *Client) GetHomeChannelData(homeChannelID string) (core.HomeChannelDataResponse, error) { +func (c *BlockchainClient) GetHomeChannelData(homeChannelID string) (core.HomeChannelDataResponse, error) { channelIDBytes, err := hexToBytes32(homeChannelID) if err != nil { return core.HomeChannelDataResponse{}, errors.Wrap(err, "invalid channel ID") @@ -214,7 +219,7 @@ func (c *Client) GetHomeChannelData(homeChannelID string) (core.HomeChannelDataR }, nil } -func (c *Client) GetEscrowDepositData(escrowChannelID string) (core.EscrowDepositDataResponse, error) { +func (c *BlockchainClient) GetEscrowDepositData(escrowChannelID string) (core.EscrowDepositDataResponse, error) { escrowIDBytes, err := hexToBytes32(escrowChannelID) if err != nil { return core.EscrowDepositDataResponse{}, errors.Wrap(err, "invalid escrow ID") @@ -232,14 +237,14 @@ func (c *Client) GetEscrowDepositData(escrowChannelID string) (core.EscrowDeposi return core.EscrowDepositDataResponse{ EscrowChannelID: escrowChannelID, - Node: c.contractAddress.Hex(), + Node: c.channelHubContractAddress.Hex(), LastState: *lastState, UnlockExpiry: data.UnlockAt, ChallengeExpiry: data.ChallengeExpiry, }, nil } -func (c *Client) GetEscrowWithdrawalData(escrowChannelID string) (core.EscrowWithdrawalDataResponse, error) { +func (c *BlockchainClient) GetEscrowWithdrawalData(escrowChannelID string) (core.EscrowWithdrawalDataResponse, error) { escrowIDBytes, err := hexToBytes32(escrowChannelID) if err != nil { return core.EscrowWithdrawalDataResponse{}, errors.Wrap(err, "invalid escrow ID") @@ -257,14 +262,14 @@ func (c *Client) GetEscrowWithdrawalData(escrowChannelID string) (core.EscrowWit return core.EscrowWithdrawalDataResponse{ EscrowChannelID: escrowChannelID, - Node: c.contractAddress.Hex(), + Node: c.channelHubContractAddress.Hex(), LastState: *lastState, }, nil } // ========= IVault Functions ========= -func (c *Client) Deposit(node, token string, amount decimal.Decimal) (string, error) { +func (c *BlockchainClient) Deposit(node, token string, amount decimal.Decimal) (string, error) { nodeAddr := common.HexToAddress(node) tokenAddr := common.HexToAddress(token) @@ -294,7 +299,7 @@ func (c *Client) Deposit(node, token string, amount decimal.Decimal) (string, er return tx.Hash().Hex(), nil } -func (c *Client) Withdraw(node, token string, amount decimal.Decimal) (string, error) { +func (c *BlockchainClient) Withdraw(node, token string, amount decimal.Decimal) (string, error) { nodeAddr := common.HexToAddress(node) tokenAddr := common.HexToAddress(token) @@ -321,7 +326,7 @@ func (c *Client) Withdraw(node, token string, amount decimal.Decimal) (string, e // ========= Getters - ERC20 ========= -func (c *Client) Approve(asset string, amount decimal.Decimal) (string, error) { +func (c *BlockchainClient) Approve(asset string, amount decimal.Decimal) (string, error) { tokenAddrHex, err := c.assetStore.GetTokenAddress(asset, c.blockchainID) if err != nil { return "", errors.Wrap(err, "failed to get token address") @@ -351,7 +356,7 @@ func (c *Client) Approve(asset string, amount decimal.Decimal) (string, error) { return "", err } - tx, err := erc20Contract.Approve(c.transactOpts, c.contractAddress, amountBig) + tx, err := erc20Contract.Approve(c.transactOpts, c.channelHubContractAddress, amountBig) if err != nil { return "", errors.Wrap(err, "failed to approve token spending") } @@ -361,7 +366,7 @@ func (c *Client) Approve(asset string, amount decimal.Decimal) (string, error) { // ========= Channel Lifecycle ========= -func (c *Client) Create(def core.ChannelDefinition, initCCS core.State) (string, error) { +func (c *BlockchainClient) Create(def core.ChannelDefinition, initCCS core.State) (string, error) { contractDef, err := coreDefToContractDef(def, initCCS.Asset, initCCS.UserWallet, c.nodeAddress) if err != nil { return "", errors.Wrap(err, "failed to convert channel definition") @@ -421,7 +426,7 @@ func (c *Client) Create(def core.ChannelDefinition, initCCS core.State) (string, return tx.Hash().Hex(), nil } -func (c *Client) MigrateChannelHere(def core.ChannelDefinition, candidate core.State) (string, error) { +func (c *BlockchainClient) MigrateChannelHere(def core.ChannelDefinition, candidate core.State) (string, error) { contractDef, err := coreDefToContractDef(def, candidate.Asset, candidate.UserWallet, c.nodeAddress) if err != nil { return "", errors.Wrap(err, "failed to convert channel definition") @@ -444,7 +449,7 @@ func (c *Client) MigrateChannelHere(def core.ChannelDefinition, candidate core.S return tx.Hash().Hex(), nil } -func (c *Client) Checkpoint(candidate core.State) (string, error) { +func (c *BlockchainClient) Checkpoint(candidate core.State) (string, error) { if candidate.HomeChannelID == nil { return "", errors.New("candidate state must have a home channel ID") } @@ -511,7 +516,7 @@ func (c *Client) Checkpoint(candidate core.State) (string, error) { return tx.Hash().Hex(), nil } -func (c *Client) Challenge(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { +func (c *BlockchainClient) Challenge(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { if candidate.HomeChannelID == nil { return "", errors.New("candidate state must have a home channel ID") } @@ -539,7 +544,7 @@ func (c *Client) Challenge(candidate core.State, challengerSig []byte, challenge return tx.Hash().Hex(), nil } -func (c *Client) Close(candidate core.State) (string, error) { +func (c *BlockchainClient) Close(candidate core.State) (string, error) { if candidate.HomeChannelID == nil { return "", errors.New("candidate state must have a home channel ID") } @@ -573,7 +578,7 @@ func (c *Client) Close(candidate core.State) (string, error) { // ========= Escrow Deposit ========= -func (c *Client) InitiateEscrowDeposit(def core.ChannelDefinition, initCCS core.State) (string, error) { +func (c *BlockchainClient) InitiateEscrowDeposit(def core.ChannelDefinition, initCCS core.State) (string, error) { contractDef, err := coreDefToContractDef(def, initCCS.Asset, initCCS.UserWallet, c.nodeAddress) if err != nil { return "", errors.Wrap(err, "failed to convert channel definition") @@ -600,7 +605,7 @@ func (c *Client) InitiateEscrowDeposit(def core.ChannelDefinition, initCCS core. return tx.Hash().Hex(), nil } -func (c *Client) ChallengeEscrowDeposit(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { +func (c *BlockchainClient) ChallengeEscrowDeposit(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } @@ -622,7 +627,7 @@ func (c *Client) ChallengeEscrowDeposit(candidate core.State, challengerSig []by return tx.Hash().Hex(), nil } -func (c *Client) FinalizeEscrowDeposit(candidate core.State) (string, error) { +func (c *BlockchainClient) FinalizeEscrowDeposit(candidate core.State) (string, error) { if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } @@ -655,7 +660,7 @@ func (c *Client) FinalizeEscrowDeposit(candidate core.State) (string, error) { // ========= Escrow Withdrawal ========= -func (c *Client) InitiateEscrowWithdrawal(def core.ChannelDefinition, initCCS core.State) (string, error) { +func (c *BlockchainClient) InitiateEscrowWithdrawal(def core.ChannelDefinition, initCCS core.State) (string, error) { contractDef, err := coreDefToContractDef(def, initCCS.Asset, initCCS.UserWallet, c.nodeAddress) if err != nil { return "", errors.Wrap(err, "failed to convert channel definition") @@ -682,7 +687,7 @@ func (c *Client) InitiateEscrowWithdrawal(def core.ChannelDefinition, initCCS co return tx.Hash().Hex(), nil } -func (c *Client) ChallengeEscrowWithdrawal(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { +func (c *BlockchainClient) ChallengeEscrowWithdrawal(candidate core.State, challengerSig []byte, challengerIdx core.ChannelParticipant) (string, error) { if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } @@ -704,7 +709,7 @@ func (c *Client) ChallengeEscrowWithdrawal(candidate core.State, challengerSig [ return tx.Hash().Hex(), nil } -func (c *Client) FinalizeEscrowWithdrawal(candidate core.State) (string, error) { +func (c *BlockchainClient) FinalizeEscrowWithdrawal(candidate core.State) (string, error) { if candidate.EscrowChannelID == nil { return "", errors.New("candidate state must have an escrow channel ID") } @@ -737,3 +742,50 @@ func (c *Client) FinalizeEscrowWithdrawal(candidate core.State) (string, error) return tx.Hash().Hex(), nil } + +func (c *BlockchainClient) EnsureSigValidatorRegistered(validatorID uint8, validatorAddress string, checkOnly bool) error { + validatorAddr := common.HexToAddress(validatorAddress) + + _validatorAddr, err := c.contract.GetNodeValidator(nil, c.nodeAddress, validatorID) + if err != nil { + return errors.Wrapf(err, "failed to check if validator %d is registered", validatorID) + } + if _validatorAddr.Hex() == validatorAddr.Hex() { + return nil + } else if _validatorAddr != (common.Address{}) { + return errors.Errorf("validator ID %d is already registered with a different address %s", validatorID, _validatorAddr.Hex()) + } + + if checkOnly { + return errors.Errorf("validator ID %d with address %s is not registered; run 'clearnode operator register-validator' to register", validatorID, validatorAddress) + } + + if err := c.checkFeeFn(context.Background(), c.transactOpts.From); err != nil { + return err + } + + uint8Type, _ := abi.NewType("uint8", "", nil) + addressType, _ := abi.NewType("address", "", nil) + uint256Type, _ := abi.NewType("uint256", "", nil) + args := abi.Arguments{ + {Type: uint8Type}, + {Type: addressType}, + {Type: uint256Type}, + } + message, err := args.Pack(validatorID, validatorAddr, new(big.Int).SetUint64(c.blockchainID)) + if err != nil { + return errors.Wrap(err, "failed to encode validator registration message") + } + + sig, err := c.txSigner.Sign(sign.ComputeEthereumSignedMessageHash(message)) + if err != nil { + return errors.Wrap(err, "failed to sign validator registration message") + } + + _, err = c.contract.RegisterNodeValidator(c.transactOpts, c.nodeAddress, validatorID, validatorAddr, sig) + if err != nil { + return errors.Wrapf(err, "failed to register validator %d with address %s", validatorID, validatorAddress) + } + + return nil +} diff --git a/pkg/blockchain/evm/client_test.go b/pkg/blockchain/evm/blockchain_test.go similarity index 87% rename from pkg/blockchain/evm/client_test.go rename to pkg/blockchain/evm/blockchain_test.go index 691129fed..8d3318015 100644 --- a/pkg/blockchain/evm/client_test.go +++ b/pkg/blockchain/evm/blockchain_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -47,7 +47,7 @@ func (m *MockPublicKey) Bytes() []byte { return m.pubBytes } -func TestNewClient(t *testing.T) { +func TestNewBlockchainClient(t *testing.T) { t.Parallel() mockEVMClient := new(MockEVMClient) mockAssetStore := new(MockAssetStore) @@ -60,7 +60,7 @@ func TestNewClient(t *testing.T) { blockchainID := uint64(1337) // Mock models simulate an EVM client where NewClient/NewChannelHub return a local struct without external calls - client, err := NewClient( + client, err := NewBlockchainClient( contractAddress, mockEVMClient, mockSigner, @@ -75,7 +75,7 @@ func TestNewClient(t *testing.T) { mock.AssertExpectationsForObjects(t, mockEVMClient, mockAssetStore, mockSigner) } -func TestClient_GetAccountsBalances(t *testing.T) { +func TestBlockchainClient_GetAccountsBalances(t *testing.T) { t.Parallel() mockEVMClient := new(MockEVMClient) mockAssetStore := new(MockAssetStore) @@ -84,7 +84,7 @@ func TestClient_GetAccountsBalances(t *testing.T) { setupMockSigner(t, mockSigner) contractAddress := common.HexToAddress("0xContract") - client, err := NewClient(contractAddress, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) + client, err := NewBlockchainClient(contractAddress, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) require.NoError(t, err) accounts := []string{"0xUser1", "0xUser2"} @@ -104,7 +104,7 @@ func TestClient_GetAccountsBalances(t *testing.T) { mock.AssertExpectationsForObjects(t, mockEVMClient, mockAssetStore, mockSigner) } -func TestClient_GetNodeBalance(t *testing.T) { +func TestBlockchainClient_GetNodeBalance(t *testing.T) { t.Parallel() mockEVMClient := new(MockEVMClient) mockAssetStore := new(MockAssetStore) @@ -112,7 +112,7 @@ func TestClient_GetNodeBalance(t *testing.T) { setupMockSigner(t, mockSigner) - client, err := NewClient(common.Address{}, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) + client, err := NewBlockchainClient(common.Address{}, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) require.NoError(t, err) token := "0xToken" @@ -129,7 +129,7 @@ func TestClient_GetNodeBalance(t *testing.T) { mock.AssertExpectationsForObjects(t, mockEVMClient, mockAssetStore, mockSigner) } -func TestClient_GetOpenChannels(t *testing.T) { +func TestBlockchainClient_GetOpenChannels(t *testing.T) { t.Parallel() mockEVMClient := new(MockEVMClient) mockAssetStore := new(MockAssetStore) @@ -137,7 +137,7 @@ func TestClient_GetOpenChannels(t *testing.T) { setupMockSigner(t, mockSigner) - client, err := NewClient(common.Address{}, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) + client, err := NewBlockchainClient(common.Address{}, mockEVMClient, mockSigner, 1, "0xNode", mockAssetStore) require.NoError(t, err) // Mock GetOpenChannels return: bytes32[] diff --git a/pkg/blockchain/evm/channel_hub.go b/pkg/blockchain/evm/channel_hub_abi.go similarity index 100% rename from pkg/blockchain/evm/channel_hub.go rename to pkg/blockchain/evm/channel_hub_abi.go diff --git a/pkg/blockchain/evm/reactor.go b/pkg/blockchain/evm/channel_hub_reactor.go similarity index 62% rename from pkg/blockchain/evm/reactor.go rename to pkg/blockchain/evm/channel_hub_reactor.go index be3bce69f..5bd6db8c4 100644 --- a/pkg/blockchain/evm/reactor.go +++ b/pkg/blockchain/evm/channel_hub_reactor.go @@ -10,40 +10,40 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) -var contractAbi *abi.ABI -var contractFilterer *ChannelHubFilterer -var eventMapping map[common.Hash]string +var channelHubAbi *abi.ABI +var channelHubFilterer *ChannelHubFilterer +var channelHubEventMapping map[common.Hash]string -func init() { +func initChannelHub() { var err error - contractAbi, err = ChannelHubMetaData.GetAbi() + channelHubAbi, err = ChannelHubMetaData.GetAbi() if err != nil { panic(err) } // Create a filterer for parsing events (address not needed for parsing) - contract := bind.NewBoundContract(common.Address{}, *contractAbi, nil, nil, nil) - contractFilterer = &ChannelHubFilterer{contract: contract} + contract := bind.NewBoundContract(common.Address{}, *channelHubAbi, nil, nil, nil) + channelHubFilterer = &ChannelHubFilterer{contract: contract} - eventMapping = make(map[common.Hash]string) - for name, event := range contractAbi.Events { - eventMapping[event.ID] = name + channelHubEventMapping = make(map[common.Hash]string) + for name, event := range channelHubAbi.Events { + channelHubEventMapping[event.ID] = name } } -type Reactor struct { +type ChannelHubReactor struct { blockchainID uint64 - eventHandler core.BlockchainEventHandler + eventHandler core.ChannelHubEventHandler storeContractEvent StoreContractEvent onEventProcessed func(blockchainID uint64, success bool) } -func NewReactor(blockchainID uint64, eventHandler core.BlockchainEventHandler, storeContractEvent StoreContractEvent) *Reactor { - return &Reactor{ +func NewChannelHubReactor(blockchainID uint64, eventHandler core.ChannelHubEventHandler, storeContractEvent StoreContractEvent) *ChannelHubReactor { + return &ChannelHubReactor{ blockchainID: blockchainID, eventHandler: eventHandler, storeContractEvent: storeContractEvent, @@ -51,78 +51,79 @@ func NewReactor(blockchainID uint64, eventHandler core.BlockchainEventHandler, s } // SetOnEventProcessed sets an optional callback invoked after each event is processed. -func (r *Reactor) SetOnEventProcessed(fn func(blockchainID uint64, success bool)) { +func (r *ChannelHubReactor) SetOnEventProcessed(fn func(blockchainID uint64, success bool)) { r.onEventProcessed = fn } -func (r *Reactor) HandleEvent(ctx context.Context, l types.Log) { +func (r *ChannelHubReactor) HandleEvent(ctx context.Context, l types.Log) error { logger := log.FromContext(ctx) eventID := l.Topics[0] - eventName, ok := eventMapping[eventID] + eventName, ok := channelHubEventMapping[eventID] if !ok { logger.Warn("unknown event ID", "eventID", eventID.Hex(), "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) - return + return nil } logger.Debug("received event", "name", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) var err error switch eventID { - case contractAbi.Events["ChannelCreated"].ID: + case channelHubAbi.Events["ChannelCreated"].ID: err = r.handleHomeChannelCreated(ctx, l) - case contractAbi.Events["ChannelCheckpointed"].ID: + case channelHubAbi.Events["ChannelCheckpointed"].ID: err = r.handleHomeChannelCheckpointed(ctx, l) - case contractAbi.Events["ChannelDeposited"].ID: + case channelHubAbi.Events["ChannelDeposited"].ID: err = r.handleChannelDeposited(ctx, l) - case contractAbi.Events["ChannelWithdrawn"].ID: + case channelHubAbi.Events["ChannelWithdrawn"].ID: err = r.handleChannelWithdrawn(ctx, l) - case contractAbi.Events["ChannelChallenged"].ID: + case channelHubAbi.Events["ChannelChallenged"].ID: err = r.handleHomeChannelChallenged(ctx, l) - case contractAbi.Events["ChannelClosed"].ID: + case channelHubAbi.Events["ChannelClosed"].ID: err = r.handleHomeChannelClosed(ctx, l) - case contractAbi.Events["EscrowDepositInitiated"].ID: + case channelHubAbi.Events["EscrowDepositInitiated"].ID: err = r.handleEscrowDepositInitiated(ctx, l) - case contractAbi.Events["EscrowDepositChallenged"].ID: + case channelHubAbi.Events["EscrowDepositChallenged"].ID: err = r.handleEscrowDepositChallenged(ctx, l) - case contractAbi.Events["EscrowDepositFinalized"].ID: + case channelHubAbi.Events["EscrowDepositFinalized"].ID: err = r.handleEscrowDepositFinalized(ctx, l) - case contractAbi.Events["EscrowWithdrawalInitiated"].ID: + case channelHubAbi.Events["EscrowWithdrawalInitiated"].ID: err = r.handleEscrowWithdrawalInitiated(ctx, l) - case contractAbi.Events["EscrowWithdrawalChallenged"].ID: + case channelHubAbi.Events["EscrowWithdrawalChallenged"].ID: err = r.handleEscrowWithdrawalChallenged(ctx, l) - case contractAbi.Events["EscrowWithdrawalFinalized"].ID: + case channelHubAbi.Events["EscrowWithdrawalFinalized"].ID: err = r.handleEscrowWithdrawalFinalized(ctx, l) - case contractAbi.Events["EscrowDepositInitiatedOnHome"].ID: + case channelHubAbi.Events["EscrowDepositInitiatedOnHome"].ID: err = r.handleEscrowDepositInitiatedOnHome(ctx, l) - case contractAbi.Events["EscrowDepositFinalizedOnHome"].ID: + case channelHubAbi.Events["EscrowDepositFinalizedOnHome"].ID: err = r.handleEscrowDepositFinalizedOnHome(ctx, l) - case contractAbi.Events["EscrowWithdrawalInitiatedOnHome"].ID: + case channelHubAbi.Events["EscrowWithdrawalInitiatedOnHome"].ID: err = r.handleEscrowWithdrawalInitiatedOnHome(ctx, l) - case contractAbi.Events["EscrowWithdrawalFinalizedOnHome"].ID: + case channelHubAbi.Events["EscrowWithdrawalFinalizedOnHome"].ID: err = r.handleEscrowWithdrawalFinalizedOnHome(ctx, l) // NOTE: Unimplemented handlers: - case contractAbi.Events["MigrationInInitiated"].ID: + case channelHubAbi.Events["MigrationInInitiated"].ID: err = r.handleHomeChannelMigrated(ctx, l) - case contractAbi.Events["MigrationInFinalized"].ID: + case channelHubAbi.Events["MigrationInFinalized"].ID: err = r.handleMigrationInFinalized(ctx, l) - case contractAbi.Events["MigrationOutInitiated"].ID: + case channelHubAbi.Events["MigrationOutInitiated"].ID: err = r.handleMigrationOutInitiated(ctx, l) - case contractAbi.Events["MigrationOutFinalized"].ID: + case channelHubAbi.Events["MigrationOutFinalized"].ID: err = r.handleMigrationOutFinalized(ctx, l) - case contractAbi.Events["Deposited"].ID: + case channelHubAbi.Events["Deposited"].ID: err = r.handleDeposited(ctx, l) - case contractAbi.Events["Withdrawn"].ID: + case channelHubAbi.Events["Withdrawn"].ID: err = r.handleWithdrawn(ctx, l) - case contractAbi.Events["EscrowDepositsPurged"].ID: + case channelHubAbi.Events["EscrowDepositsPurged"].ID: err = r.handleEscrowDepositsPurged(ctx, l) default: - err = errors.New("unknown event: " + eventID.Hex()) + logger.Warn("unknown event: " + eventID.Hex()) } if r.onEventProcessed != nil { r.onEventProcessed(r.blockchainID, err == nil) } if err != nil { logger.Warn("error processing event", "error", err) + return errors.Wrap(err, "error processing event") } if err := r.storeContractEvent(core.BlockchainEvent{ @@ -134,13 +135,15 @@ func (r *Reactor) HandleEvent(ctx context.Context, l types.Log) { LogIndex: uint32(l.Index), }); err != nil { logger.Warn("error storing contract event", "error", err, "event", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + return errors.Wrap(err, "error storing contract event") } logger.Info("processed event", "event", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + return nil } -func (r *Reactor) handleHomeChannelCreated(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelCreated(l) +func (r *ChannelHubReactor) handleHomeChannelCreated(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelCreated(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelCreated event") } @@ -152,8 +155,8 @@ func (r *Reactor) handleHomeChannelCreated(ctx context.Context, l types.Log) err return r.eventHandler.HandleHomeChannelCreated(ctx, &ev) } -func (r *Reactor) handleHomeChannelMigrated(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseMigrationInInitiated(l) +func (r *ChannelHubReactor) handleHomeChannelMigrated(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseMigrationInInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationInInitiated event") } @@ -165,8 +168,8 @@ func (r *Reactor) handleHomeChannelMigrated(ctx context.Context, l types.Log) er return r.eventHandler.HandleHomeChannelMigrated(ctx, &ev) } -func (r *Reactor) handleHomeChannelCheckpointed(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelCheckpointed(l) +func (r *ChannelHubReactor) handleHomeChannelCheckpointed(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelCheckpointed(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelCheckpointed event") } @@ -178,8 +181,8 @@ func (r *Reactor) handleHomeChannelCheckpointed(ctx context.Context, l types.Log return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleChannelDeposited(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelDeposited(l) +func (r *ChannelHubReactor) handleChannelDeposited(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelDeposited(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelDeposited event") } @@ -191,8 +194,8 @@ func (r *Reactor) handleChannelDeposited(ctx context.Context, l types.Log) error return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleChannelWithdrawn(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelWithdrawn(l) +func (r *ChannelHubReactor) handleChannelWithdrawn(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelWithdrawn(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelWithdrawn event") } @@ -204,8 +207,8 @@ func (r *Reactor) handleChannelWithdrawn(ctx context.Context, l types.Log) error return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleHomeChannelChallenged(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelChallenged(l) +func (r *ChannelHubReactor) handleHomeChannelChallenged(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelChallenged event") } @@ -218,8 +221,8 @@ func (r *Reactor) handleHomeChannelChallenged(ctx context.Context, l types.Log) return r.eventHandler.HandleHomeChannelChallenged(ctx, &ev) } -func (r *Reactor) handleHomeChannelClosed(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseChannelClosed(l) +func (r *ChannelHubReactor) handleHomeChannelClosed(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseChannelClosed(l) if err != nil { return errors.Wrap(err, "failed to parse ChannelClosed event") } @@ -231,8 +234,8 @@ func (r *Reactor) handleHomeChannelClosed(ctx context.Context, l types.Log) erro return r.eventHandler.HandleHomeChannelClosed(ctx, &ev) } -func (r *Reactor) handleEscrowDepositInitiated(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositInitiated(l) +func (r *ChannelHubReactor) handleEscrowDepositInitiated(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositInitiated event") } @@ -244,8 +247,8 @@ func (r *Reactor) handleEscrowDepositInitiated(ctx context.Context, l types.Log) return r.eventHandler.HandleEscrowDepositInitiated(ctx, &ev) } -func (r *Reactor) handleEscrowDepositChallenged(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositChallenged(l) +func (r *ChannelHubReactor) handleEscrowDepositChallenged(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositChallenged event") } @@ -258,8 +261,8 @@ func (r *Reactor) handleEscrowDepositChallenged(ctx context.Context, l types.Log return r.eventHandler.HandleEscrowDepositChallenged(ctx, &ev) } -func (r *Reactor) handleEscrowDepositFinalized(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositFinalized(l) +func (r *ChannelHubReactor) handleEscrowDepositFinalized(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositFinalized event") } @@ -271,8 +274,8 @@ func (r *Reactor) handleEscrowDepositFinalized(ctx context.Context, l types.Log) return r.eventHandler.HandleEscrowDepositFinalized(ctx, &ev) } -func (r *Reactor) handleEscrowWithdrawalInitiated(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowWithdrawalInitiated(l) +func (r *ChannelHubReactor) handleEscrowWithdrawalInitiated(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowWithdrawalInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalInitiated event") } @@ -284,8 +287,8 @@ func (r *Reactor) handleEscrowWithdrawalInitiated(ctx context.Context, l types.L return r.eventHandler.HandleEscrowWithdrawalInitiated(ctx, &ev) } -func (r *Reactor) handleEscrowWithdrawalChallenged(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowWithdrawalChallenged(l) +func (r *ChannelHubReactor) handleEscrowWithdrawalChallenged(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowWithdrawalChallenged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalChallenged event") } @@ -298,8 +301,8 @@ func (r *Reactor) handleEscrowWithdrawalChallenged(ctx context.Context, l types. return r.eventHandler.HandleEscrowWithdrawalChallenged(ctx, &ev) } -func (r *Reactor) handleEscrowWithdrawalFinalized(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowWithdrawalFinalized(l) +func (r *ChannelHubReactor) handleEscrowWithdrawalFinalized(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowWithdrawalFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalFinalized event") } @@ -311,8 +314,8 @@ func (r *Reactor) handleEscrowWithdrawalFinalized(ctx context.Context, l types.L return r.eventHandler.HandleEscrowWithdrawalFinalized(ctx, &ev) } -func (r *Reactor) handleEscrowDepositInitiatedOnHome(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositInitiatedOnHome(l) +func (r *ChannelHubReactor) handleEscrowDepositInitiatedOnHome(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositInitiatedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositInitiatedOnHome event") } @@ -324,8 +327,8 @@ func (r *Reactor) handleEscrowDepositInitiatedOnHome(ctx context.Context, l type return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleEscrowDepositFinalizedOnHome(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositFinalizedOnHome(l) +func (r *ChannelHubReactor) handleEscrowDepositFinalizedOnHome(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositFinalizedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositFinalizedOnHome event") } @@ -337,8 +340,8 @@ func (r *Reactor) handleEscrowDepositFinalizedOnHome(ctx context.Context, l type return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowWithdrawalInitiatedOnHome(l) +func (r *ChannelHubReactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowWithdrawalInitiatedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalInitiatedOnHome event") } @@ -350,8 +353,8 @@ func (r *Reactor) handleEscrowWithdrawalInitiatedOnHome(ctx context.Context, l t return r.eventHandler.HandleHomeChannelCheckpointed(ctx, &ev) } -func (r *Reactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowWithdrawalFinalizedOnHome(l) +func (r *ChannelHubReactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowWithdrawalFinalizedOnHome(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowWithdrawalFinalizedOnHome event") } @@ -365,8 +368,8 @@ func (r *Reactor) handleEscrowWithdrawalFinalizedOnHome(ctx context.Context, l t // Additional event handlers for events not yet defined in core.BlockchainEventHandler -func (r *Reactor) handleMigrationInFinalized(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseMigrationInFinalized(l) +func (r *ChannelHubReactor) handleMigrationInFinalized(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseMigrationInFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationInFinalized event") } @@ -378,8 +381,8 @@ func (r *Reactor) handleMigrationInFinalized(ctx context.Context, l types.Log) e return nil } -func (r *Reactor) handleMigrationOutInitiated(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseMigrationOutInitiated(l) +func (r *ChannelHubReactor) handleMigrationOutInitiated(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseMigrationOutInitiated(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationOutInitiated event") } @@ -391,8 +394,8 @@ func (r *Reactor) handleMigrationOutInitiated(ctx context.Context, l types.Log) return nil } -func (r *Reactor) handleMigrationOutFinalized(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseMigrationOutFinalized(l) +func (r *ChannelHubReactor) handleMigrationOutFinalized(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseMigrationOutFinalized(l) if err != nil { return errors.Wrap(err, "failed to parse MigrationOutFinalized event") } @@ -404,8 +407,8 @@ func (r *Reactor) handleMigrationOutFinalized(ctx context.Context, l types.Log) return nil } -func (r *Reactor) handleDeposited(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseDeposited(l) +func (r *ChannelHubReactor) handleDeposited(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseDeposited(l) if err != nil { return errors.Wrap(err, "failed to parse Deposited event") } @@ -418,8 +421,8 @@ func (r *Reactor) handleDeposited(ctx context.Context, l types.Log) error { return nil } -func (r *Reactor) handleWithdrawn(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseWithdrawn(l) +func (r *ChannelHubReactor) handleWithdrawn(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseWithdrawn(l) if err != nil { return errors.Wrap(err, "failed to parse Withdrawn event") } @@ -432,8 +435,8 @@ func (r *Reactor) handleWithdrawn(ctx context.Context, l types.Log) error { return nil } -func (r *Reactor) handleEscrowDepositsPurged(ctx context.Context, l types.Log) error { - event, err := contractFilterer.ParseEscrowDepositsPurged(l) +func (r *ChannelHubReactor) handleEscrowDepositsPurged(ctx context.Context, l types.Log) error { + event, err := channelHubFilterer.ParseEscrowDepositsPurged(l) if err != nil { return errors.Wrap(err, "failed to parse EscrowDepositsPurged event") } diff --git a/pkg/blockchain/evm/client_opts.go b/pkg/blockchain/evm/client_opts.go index 3aa4bc832..fe9368721 100644 --- a/pkg/blockchain/evm/client_opts.go +++ b/pkg/blockchain/evm/client_opts.go @@ -11,7 +11,7 @@ type ClientAllowanceCheck struct { RequireAllowanceCheck bool } -func (ch ClientAllowanceCheck) apply(c *Client) { +func (ch ClientAllowanceCheck) apply(c *BlockchainClient) { c.requireCheckAllowance = ch.RequireAllowanceCheck } @@ -19,7 +19,7 @@ type ClientBalanceCheck struct { RequireBalanceCheck bool } -func (ch ClientBalanceCheck) apply(c *Client) { +func (ch ClientBalanceCheck) apply(c *BlockchainClient) { c.requireCheckBalance = ch.RequireBalanceCheck } @@ -27,7 +27,7 @@ type ClientFeeCheck struct { RequirePositiveNativeBalance bool } -func (ch ClientFeeCheck) apply(c *Client) { +func (ch ClientFeeCheck) apply(c *BlockchainClient) { if !ch.RequirePositiveNativeBalance { c.checkFeeFn = func(ctx context.Context, account common.Address) error { return nil diff --git a/pkg/blockchain/evm/erc20.go b/pkg/blockchain/evm/erc20_abi.go similarity index 100% rename from pkg/blockchain/evm/erc20.go rename to pkg/blockchain/evm/erc20_abi.go diff --git a/pkg/blockchain/evm/init.go b/pkg/blockchain/evm/init.go new file mode 100644 index 000000000..a042d718b --- /dev/null +++ b/pkg/blockchain/evm/init.go @@ -0,0 +1,6 @@ +package evm + +func init() { + initChannelHub() + initLockingContract() +} diff --git a/pkg/blockchain/evm/interface.go b/pkg/blockchain/evm/interface.go index 84810ec7a..9a28ccd97 100644 --- a/pkg/blockchain/evm/interface.go +++ b/pkg/blockchain/evm/interface.go @@ -3,13 +3,13 @@ package evm import ( "context" - "github.com/erc7824/nitrolite/pkg/core" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" + "github.com/layer-3/nitrolite/pkg/core" ) -type HandleEvent func(ctx context.Context, eventLog types.Log) +type HandleEvent func(ctx context.Context, eventLog types.Log) error type StoreContractEvent func(ev core.BlockchainEvent) error type LatestEventGetter func(contractAddress string, blockchainID uint64) (ev core.BlockchainEvent, err error) diff --git a/pkg/blockchain/evm/listener.go b/pkg/blockchain/evm/listener.go index 474689030..1fcf9efc2 100644 --- a/pkg/blockchain/evm/listener.go +++ b/pkg/blockchain/evm/listener.go @@ -7,13 +7,13 @@ import ( "sync/atomic" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" ) const ( @@ -72,8 +72,7 @@ func (l *Listener) Listen(ctx context.Context, handleClosure func(err error)) { } go func() { - defer childHandleClosure(nil) - l.listenEvents(childCtx) + childHandleClosure(l.listenEvents(childCtx)) }() go func() { @@ -87,11 +86,10 @@ func (l *Listener) Listen(ctx context.Context, handleClosure func(err error)) { } // listenEvents listens for blockchain events and processes them with the provided handler -func (l *Listener) listenEvents(ctx context.Context) { +func (l *Listener) listenEvents(ctx context.Context) error { ev, err := l.getLatestEvent(l.contractAddress.String(), l.blockchainID) if err != nil { l.logger.Error("failed to get latest processed event", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) - return } lastBlock := ev.BlockNumber lastIndex := ev.LogIndex @@ -151,18 +149,23 @@ func (l *Listener) listenEvents(ctx context.Context) { select { case <-ctx.Done(): l.logger.Info("stopping event listener", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) - return + eventSubscription.Unsubscribe() + return nil case eventLog := <-historicalCh: l.logger.Debug("received new event", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String(), "blockNumber", lastBlock, "logIndex", eventLog.Index) ctx := log.SetContextLogger(context.Background(), l.logger) - l.handleEvent(ctx, eventLog) + if err := l.handleEvent(ctx, eventLog); err != nil { + return err + } case eventLog := <-currentCh: lastBlock = eventLog.BlockNumber l.logger.Debug("received new event", "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String(), "blockNumber", lastBlock, "logIndex", eventLog.Index) ctx := log.SetContextLogger(context.Background(), l.logger) - l.handleEvent(ctx, eventLog) + if err := l.handleEvent(ctx, eventLog); err != nil { + return err + } case err := <-eventSubscription.Err(): if err != nil { l.logger.Error("event subscription error", "error", err, "blockchainID", l.blockchainID, "contractAddress", l.contractAddress.String()) diff --git a/pkg/blockchain/evm/listener_test.go b/pkg/blockchain/evm/listener_test.go index dd4d5a3f1..3e787b254 100644 --- a/pkg/blockchain/evm/listener_test.go +++ b/pkg/blockchain/evm/listener_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -64,8 +64,9 @@ func TestListener_Listen_CurrentEvents(t *testing.T) { // Channel to signal event handling eventHandled := make(chan struct{}) - handleEvent := func(ctx context.Context, log types.Log) { + handleEvent := func(ctx context.Context, log types.Log) error { close(eventHandled) + return nil } listener := NewListener(addr, mockClient, 1, 100, logger, handleEvent, getLatestEvent) @@ -157,7 +158,7 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { var receivedCount int64 doneCh := make(chan struct{}) - handleEvent := func(ctx context.Context, log types.Log) { + handleEvent := func(ctx context.Context, log types.Log) error { count := atomic.AddInt64(&receivedCount, 1) if count >= 2 { // Expect 1 historical + 1 current select { @@ -166,6 +167,8 @@ func TestListener_Listen_HistoricalAndCurrent(t *testing.T) { close(doneCh) } } + + return nil } listener := NewListener(addr, mockClient, 1, 10, logger, handleEvent, getLatestEvent) diff --git a/pkg/blockchain/evm/locking_client.go b/pkg/blockchain/evm/locking_client.go new file mode 100644 index 000000000..56a49dcd0 --- /dev/null +++ b/pkg/blockchain/evm/locking_client.go @@ -0,0 +1,232 @@ +package evm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// LockingClient provides access to a Locking contract. +type LockingClient struct { + BaseClient + lockingContractAddress common.Address + transactOpts *bind.TransactOpts + + tokenAddress common.Address + tokenDecimals uint8 +} + +// NewLockingClient creates a new LockingClient. +// If txSigner is provided, the client can perform write operations (lock, relock, unlock, withdraw). +func NewLockingClient(lockingContractAddress common.Address, evmClient EVMClient, blockchainID uint64, txSigner ...sign.Signer) (*LockingClient, error) { + c := &LockingClient{ + BaseClient: BaseClient{ + evmClient: evmClient, + blockchainID: blockchainID, + }, + lockingContractAddress: lockingContractAddress, + } + if len(txSigner) > 0 && txSigner[0] != nil { + c.transactOpts = signerTxOpts(txSigner[0], blockchainID) + } + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return nil, errors.Wrap(err, "failed to instantiate Locking contract") + } + + tokenAddress, err := lockingContract.Asset(nil) + if err != nil { + return nil, errors.Wrap(err, "failed to get asset from the Locking contract") + } + + erc20Contract, err := NewIERC20(tokenAddress, c.evmClient) + if err != nil { + return nil, errors.Wrap(err, "failed to instantiate ERC20 contract") + } + + decimals, err := erc20Contract.Decimals(nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to get decimals for token %s", tokenAddress.Hex()) + } + + c.tokenAddress = tokenAddress + c.tokenDecimals = decimals + + return c, nil +} + +// GetTokenDecimals returns the number of decimals for the token used in the AppRegistry. +// This is needed to convert between human-readable amounts and the raw integer amounts used in transactions. +func (c *LockingClient) GetTokenDecimals() (uint8, error) { + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return 0, errors.Wrap(err, "failed to instantiate Locking contract") + } + + tokenAddress, err := lockingContract.Asset(&bind.CallOpts{}) + if err != nil { + return 0, errors.Wrap(err, "failed to get asset from the Locking contract") + } + + erc20Contract, err := NewIERC20(tokenAddress, c.evmClient) + if err != nil { + return 0, errors.Wrap(err, "failed to instantiate ERC20 contract") + } + + decimals, err := erc20Contract.Decimals(&bind.CallOpts{}) + if err != nil { + return 0, errors.Wrapf(err, "failed to get decimals for token %s", tokenAddress.Hex()) + } + + return decimals, nil +} + +// Lock locks tokens into the Locking contract for the specified target address. +// The caller must have approved the Locking contract to spend the token beforehand. +func (c *LockingClient) Lock(targetWalletAddress string, amount decimal.Decimal) (string, error) { + if !common.IsHexAddress(targetWalletAddress) { + return "", errors.Errorf("invalid address %q", targetWalletAddress) + } + + targetAddr := common.HexToAddress(targetWalletAddress) + if c.transactOpts == nil { + return "", errors.New("transaction signer not configured") + } + + amountBig, err := core.DecimalToBigInt(amount, c.tokenDecimals) + if err != nil { + return "", errors.Wrap(err, "failed to convert amount with decimal precision") + } + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate Locking contract") + } + + tx, err := lockingContract.Lock(c.transactOpts, targetAddr, amountBig) + if err != nil { + return "", errors.Wrap(err, "failed to send lock transaction") + } + return tx.Hash().Hex(), nil +} + +// Relock re-locks tokens that are in the unlocking state back to the locked state. +func (c *LockingClient) Relock() (string, error) { + if c.transactOpts == nil { + return "", errors.New("transaction signer not configured") + } + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate Locking contract") + } + + tx, err := lockingContract.Relock(c.transactOpts) + if err != nil { + return "", errors.Wrap(err, "failed to send relock transaction") + } + return tx.Hash().Hex(), nil +} + +// Unlock initiates the unlock process for the caller's locked tokens. +// After the unlock period elapses, Withdraw can be called. +func (c *LockingClient) Unlock() (string, error) { + if c.transactOpts == nil { + return "", errors.New("transaction signer not configured") + } + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate Locking contract") + } + + tx, err := lockingContract.Unlock(c.transactOpts) + if err != nil { + return "", errors.Wrap(err, "failed to send unlock transaction") + } + return tx.Hash().Hex(), nil +} + +// Withdraw withdraws unlocked tokens to the specified destination address. +// Can only be called after the unlock period has elapsed. +func (c *LockingClient) Withdraw(destination string) (string, error) { + if c.transactOpts == nil { + return "", errors.New("transaction signer not configured") + } + + destinationAddr := common.HexToAddress(destination) + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate Locking contract") + } + + tx, err := lockingContract.Withdraw(c.transactOpts, destinationAddr) + if err != nil { + return "", errors.Wrap(err, "failed to send withdraw transaction") + } + return tx.Hash().Hex(), nil +} + +// ApproveToken approves the Locking contract to spend the specified amount of tokens. +// This must be called before Lock. +func (c *LockingClient) ApproveToken(amount decimal.Decimal) (string, error) { + if c.transactOpts == nil { + return "", errors.New("transaction signer not configured") + } + + amountBig, err := core.DecimalToBigInt(amount, c.tokenDecimals) + if err != nil { + return "", errors.Wrap(err, "failed to convert amount with decimal precision") + } + + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate Locking contract") + } + + tokenAddress, err := lockingContract.Asset(&bind.CallOpts{}) + if err != nil { + return "", errors.Wrap(err, "failed to get asset from Locking contract") + } + + erc20Contract, err := NewIERC20(tokenAddress, c.evmClient) + if err != nil { + return "", errors.Wrap(err, "failed to instantiate ERC20 contract") + } + + tx, err := erc20Contract.Approve(c.transactOpts, c.lockingContractAddress, amountBig) + if err != nil { + return "", errors.Wrap(err, "failed to send approve transaction") + } + return tx.Hash().Hex(), nil +} + +// GetBalance returns the locked balance of a user in the Locking contraсt. +func (c *LockingClient) GetBalance(user string) (decimal.Decimal, error) { + userAddr := common.HexToAddress(user) + lockingContract, err := NewAppRegistry(c.lockingContractAddress, c.evmClient) + if err != nil { + return decimal.Zero, errors.Wrap(err, "failed to instantiate Locking contract") + } + + balance, err := lockingContract.BalanceOf(nil, userAddr) + if err != nil { + return decimal.Zero, errors.Wrap(err, "failed to get balance") + } + + return decimal.NewFromBigInt(balance, -int32(c.tokenDecimals)), nil +} + +// decimalToBigInt converts a decimal amount to *big.Int given token decimals. +func decimalToBigInt(amount decimal.Decimal, decimals int32) *big.Int { + shifted := amount.Shift(decimals) + return shifted.BigInt() +} diff --git a/pkg/blockchain/evm/locking_reactor.go b/pkg/blockchain/evm/locking_reactor.go new file mode 100644 index 000000000..3493cd3f5 --- /dev/null +++ b/pkg/blockchain/evm/locking_reactor.go @@ -0,0 +1,164 @@ +package evm + +import ( + "context" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/shopspring/decimal" + + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" +) + +var lockingContractAbi *abi.ABI +var lockingContractFilterer *AppRegistryFilterer +var eventMapping map[common.Hash]string + +func initLockingContract() { + var err error + lockingContractAbi, err = AppRegistryMetaData.GetAbi() + if err != nil { + panic(err) + } + + // Create a filterer for parsing events (address not needed for parsing) + contract := bind.NewBoundContract(common.Address{}, *lockingContractAbi, nil, nil, nil) + lockingContractFilterer = &AppRegistryFilterer{contract: contract} + + eventMapping = make(map[common.Hash]string) + for name, event := range lockingContractAbi.Events { + eventMapping[event.ID] = name + } +} + +type LockingContractReactor struct { + blockchainID uint64 + eventHandler core.LockingContractEventHandler + storeContractEvent StoreContractEvent + tokenDecimals int32 + onEventProcessed func(blockchainID uint64, success bool) +} + +func NewLockingContractReactor(blockchainID uint64, eventHandler core.LockingContractEventHandler, getTokenDecimals func() (uint8, error), storeContractEvent StoreContractEvent) (*LockingContractReactor, error) { + tokenDecimals, err := getTokenDecimals() + if err != nil { + return nil, errors.Wrap(err, "failed to get token decimals") + } + return &LockingContractReactor{ + blockchainID: blockchainID, + eventHandler: eventHandler, + tokenDecimals: int32(tokenDecimals), + storeContractEvent: storeContractEvent, + }, nil +} + +// SetOnEventProcessed sets an optional callback invoked after each event is processed. +func (r *LockingContractReactor) SetOnEventProcessed(fn func(blockchainID uint64, success bool)) { + r.onEventProcessed = fn +} + +func (r *LockingContractReactor) HandleEvent(ctx context.Context, l types.Log) error { + logger := log.FromContext(ctx) + + eventID := l.Topics[0] + eventName, ok := eventMapping[eventID] + if !ok { + logger.Warn("unknown event ID", "eventID", eventID.Hex(), "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + return nil + } + logger.Debug("received event", "name", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + + var err error + switch eventID { + case lockingContractAbi.Events["Locked"].ID: + err = r.handleLocked(ctx, l) + case lockingContractAbi.Events["Relocked"].ID: + err = r.handleRelocked(ctx, l) + case lockingContractAbi.Events["UnlockInitiated"].ID: + err = r.handleUnlockInitiated(ctx, l) + case lockingContractAbi.Events["Withdrawn"].ID: + err = r.handleWithdrawn(ctx, l) + default: + logger.Warn("unknown event: " + eventID.Hex()) + } + if r.onEventProcessed != nil { + r.onEventProcessed(r.blockchainID, err == nil) + } + if err != nil { + logger.Warn("error processing event", "error", err) + return errors.Wrap(err, "error processing event") + } + + if err := r.storeContractEvent(core.BlockchainEvent{ + BlockNumber: l.BlockNumber, + BlockchainID: r.blockchainID, + Name: eventName, + ContractAddress: l.Address.Hex(), + TransactionHash: l.TxHash.String(), + LogIndex: uint32(l.Index), + }); err != nil { + logger.Warn("error storing contract event", "error", err, "event", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + return errors.Wrap(err, "error storing contract event") + } + + logger.Info("processed event", "event", eventName, "blockNumber", l.BlockNumber, "txHash", l.TxHash.String(), "logIndex", l.Index) + return nil +} + +func (r *LockingContractReactor) handleLocked(ctx context.Context, l types.Log) error { + event, err := lockingContractFilterer.ParseLocked(l) + if err != nil { + return errors.Wrap(err, "failed to parse Locked event") + } + + ev := core.UserLockedBalanceUpdatedEvent{ + UserAddress: strings.ToLower(event.User.String()), + BlockchainID: r.blockchainID, + Balance: decimal.NewFromBigInt(event.NewBalance, -r.tokenDecimals), + } + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) +} + +func (r *LockingContractReactor) handleRelocked(ctx context.Context, l types.Log) error { + event, err := lockingContractFilterer.ParseRelocked(l) + if err != nil { + return errors.Wrap(err, "failed to parse Relocked event") + } + ev := core.UserLockedBalanceUpdatedEvent{ + UserAddress: strings.ToLower(event.User.String()), + BlockchainID: r.blockchainID, + Balance: decimal.NewFromBigInt(event.Balance, -r.tokenDecimals), + } + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) +} + +func (r *LockingContractReactor) handleUnlockInitiated(ctx context.Context, l types.Log) error { + event, err := lockingContractFilterer.ParseUnlockInitiated(l) + if err != nil { + return errors.Wrap(err, "failed to parse Unlockinitiated event") + } + ev := core.UserLockedBalanceUpdatedEvent{ + UserAddress: strings.ToLower(event.User.String()), + BlockchainID: r.blockchainID, + Balance: decimal.Zero, + } + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) +} + +func (r *LockingContractReactor) handleWithdrawn(ctx context.Context, l types.Log) error { + event, err := lockingContractFilterer.ParseWithdrawn(l) + if err != nil { + return errors.Wrap(err, "failed to parse Withdrawn event") + } + ev := core.UserLockedBalanceUpdatedEvent{ + UserAddress: strings.ToLower(event.User.String()), + BlockchainID: r.blockchainID, + Balance: decimal.Zero, + } + return r.eventHandler.HandleUserLockedBalanceUpdated(ctx, &ev) +} diff --git a/pkg/blockchain/evm/locking_reactor_test.go b/pkg/blockchain/evm/locking_reactor_test.go new file mode 100644 index 000000000..a03389e0f --- /dev/null +++ b/pkg/blockchain/evm/locking_reactor_test.go @@ -0,0 +1,127 @@ +package evm + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitrolite/pkg/core" +) + +func TestAppRegistryReactor_HandleLocked(t *testing.T) { + userAddr := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + deposited := big.NewInt(500_000_000) // 500 USDC (6 decimals) + newBalance := big.NewInt(1_000_000_000) // 1000 USDC + var tokenDecimals int32 = 6 + blockchainID := uint64(1) + + // ABI-encode the non-indexed parameters: deposited, newBalance + depositedPadded := common.LeftPadBytes(deposited.Bytes(), 32) + newBalancePadded := common.LeftPadBytes(newBalance.Bytes(), 32) + data := append(depositedPadded, newBalancePadded...) + + lockedEventID := lockingContractAbi.Events["Locked"].ID + logEntry := types.Log{ + Topics: []common.Hash{ + lockedEventID, + common.BytesToHash(userAddr.Bytes()), // indexed user + }, + Data: data, + BlockNumber: 100, + TxHash: common.HexToHash("0xdeadbeef"), + Index: 0, + } + + t.Run("success", func(t *testing.T) { + var capturedEvent *core.UserLockedBalanceUpdatedEvent + handler := &mockAppRegistryEventHandler{ + handleFn: func(_ context.Context, ev *core.UserLockedBalanceUpdatedEvent) error { + capturedEvent = ev + return nil + }, + } + + var storedEvent core.BlockchainEvent + storeContractEvent := func(ev core.BlockchainEvent) error { + storedEvent = ev + return nil + } + + reactor, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { + return uint8(tokenDecimals), nil + }, storeContractEvent) + require.NoError(t, err) + + var processedSuccess bool + reactor.SetOnEventProcessed(func(_ uint64, success bool) { + processedSuccess = success + }) + + reactor.HandleEvent(context.Background(), logEntry) + + require.NotNil(t, capturedEvent) + assert.Equal(t, userAddr.String(), common.HexToAddress(capturedEvent.UserAddress).String()) + assert.Equal(t, blockchainID, capturedEvent.BlockchainID) + expectedBalance := decimal.NewFromBigInt(newBalance, -tokenDecimals) + assert.True(t, expectedBalance.Equal(capturedEvent.Balance), "expected %s, got %s", expectedBalance, capturedEvent.Balance) + + assert.True(t, processedSuccess) + assert.Equal(t, "Locked", storedEvent.Name) + assert.Equal(t, uint64(100), storedEvent.BlockNumber) + assert.Equal(t, blockchainID, storedEvent.BlockchainID) + }) + + t.Run("getTokenDecimals error", func(t *testing.T) { + handler := &mockAppRegistryEventHandler{ + handleFn: func(_ context.Context, _ *core.UserLockedBalanceUpdatedEvent) error { + t.Fatal("handler should not be called") + return nil + }, + } + + storeContractEvent := func(_ core.BlockchainEvent) error { return nil } + + _, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { + return 0, assert.AnError + }, storeContractEvent) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to get token decimals") + }) + + t.Run("handler error", func(t *testing.T) { + handler := &mockAppRegistryEventHandler{ + handleFn: func(_ context.Context, _ *core.UserLockedBalanceUpdatedEvent) error { + return assert.AnError + }, + } + + storeContractEvent := func(_ core.BlockchainEvent) error { return nil } + + reactor, err := NewLockingContractReactor(blockchainID, handler, func() (uint8, error) { + return uint8(tokenDecimals), nil + }, storeContractEvent) + require.NoError(t, err) + + var processedSuccess bool + reactor.SetOnEventProcessed(func(_ uint64, success bool) { + processedSuccess = success + }) + + reactor.HandleEvent(context.Background(), logEntry) + assert.False(t, processedSuccess) + }) +} + +type mockAppRegistryEventHandler struct { + handleFn func(context.Context, *core.UserLockedBalanceUpdatedEvent) error +} + +func (m *mockAppRegistryEventHandler) HandleUserLockedBalanceUpdated(ctx context.Context, ev *core.UserLockedBalanceUpdatedEvent) error { + return m.handleFn(ctx, ev) +} diff --git a/pkg/blockchain/evm/utils.go b/pkg/blockchain/evm/utils.go index dff1d8764..c118d5311 100644 --- a/pkg/blockchain/evm/utils.go +++ b/pkg/blockchain/evm/utils.go @@ -5,13 +5,13 @@ import ( "math/big" "time" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/log" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/pkg/errors" "github.com/shopspring/decimal" ) diff --git a/pkg/blockchain/evm/utils_test.go b/pkg/blockchain/evm/utils_test.go index 9e93dddda..90a487754 100644 --- a/pkg/blockchain/evm/utils_test.go +++ b/pkg/blockchain/evm/utils_test.go @@ -10,7 +10,7 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // ========= hexToBytes32 Tests ========= diff --git a/pkg/core/README.md b/pkg/core/README.md index 96d3efb31..539c1b3a7 100644 --- a/pkg/core/README.md +++ b/pkg/core/README.md @@ -82,7 +82,7 @@ The `Ledger` tracks balances and "Net Flow" (total funds inflow(+)/outflow(-) of ## Usage Example: Generating IDs ```go -import "github.com/erc7824/nitrolite/core" +import "github.com/layer-3/nitrolite/core" // Generate a Home Channel ID channelID, err := core.GetHomeChannelID( diff --git a/pkg/core/channel_signer.go b/pkg/core/channel_signer.go index dd6283589..36f9360b3 100644 --- a/pkg/core/channel_signer.go +++ b/pkg/core/channel_signer.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/sign" ) type ChannelSignerType uint8 diff --git a/pkg/core/event.go b/pkg/core/event.go index 07d8ec4dd..4d2c0bcdb 100644 --- a/pkg/core/event.go +++ b/pkg/core/event.go @@ -1,5 +1,7 @@ package core +import "github.com/shopspring/decimal" + // On-chain events // HomeChannelCreatedEvent represents the ChannelCreated event @@ -46,6 +48,12 @@ type channelChallengedEvent struct { ChallengeExpiry uint64 `json:"challenge_expiry"` } +type UserLockedBalanceUpdatedEvent struct { + UserAddress string `json:"user_address"` + BlockchainID uint64 `json:"blockchain_id"` + Balance decimal.Decimal `json:"balance"` +} + type BlockchainEvent struct { ContractAddress string `json:"contract_address"` BlockchainID uint64 `json:"blockchain_id"` diff --git a/pkg/core/interface.go b/pkg/core/interface.go index 4fabe848f..67c9b78e3 100644 --- a/pkg/core/interface.go +++ b/pkg/core/interface.go @@ -10,7 +10,7 @@ import ( // Client defines the interface for interacting with the ChannelsHub smart contract // TODO: add context to all methods -type Client interface { +type BlockchainClient interface { // Getters - IVault GetAccountsBalances(accounts []string, tokens []string) ([][]decimal.Decimal, error) @@ -29,6 +29,9 @@ type Client interface { Deposit(node, token string, amount decimal.Decimal) (string, error) Withdraw(node, token string, amount decimal.Decimal) (string, error) + // Node lifecycle + EnsureSigValidatorRegistered(validatorID uint8, validatorAddress string, checkOnly bool) error + // Channel lifecycle Create(def ChannelDefinition, initCCS State) (string, error) MigrateChannelHere(def ChannelDefinition, candidate State) (string, error) @@ -47,6 +50,19 @@ type Client interface { FinalizeEscrowWithdrawal(candidate State) (string, error) } +// ========= AppRegistryClient Interface ========= + +type AppRegistryClient interface { + ApproveToken(amount decimal.Decimal) (string, error) + GetBalance(user string) (decimal.Decimal, error) + GetTokenDecimals() (uint8, error) + + Lock(targetWallet string, amount decimal.Decimal) (string, error) + Relock() (string, error) + Unlock() (string, error) + Withdraw(destinationWallet string) (string, error) +} + // ========= TransitionValidator Interface ========= // StateAdvancer applies state transitions @@ -72,7 +88,7 @@ type AssetStore interface { } // Channel lifecycle event handlers -type BlockchainEventHandler interface { +type ChannelHubEventHandler interface { HandleHomeChannelCreated(context.Context, *HomeChannelCreatedEvent) error HandleHomeChannelMigrated(context.Context, *HomeChannelMigratedEvent) error HandleHomeChannelCheckpointed(context.Context, *HomeChannelCheckpointedEvent) error @@ -85,3 +101,7 @@ type BlockchainEventHandler interface { HandleEscrowWithdrawalChallenged(context.Context, *EscrowWithdrawalChallengedEvent) error HandleEscrowWithdrawalFinalized(context.Context, *EscrowWithdrawalFinalizedEvent) error } + +type LockingContractEventHandler interface { + HandleUserLockedBalanceUpdated(context.Context, *UserLockedBalanceUpdatedEvent) error +} diff --git a/pkg/core/session_key.go b/pkg/core/session_key.go index 39184c591..0f0b91468 100644 --- a/pkg/core/session_key.go +++ b/pkg/core/session_key.go @@ -6,11 +6,11 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" ) // ChannelSessionKeyStateV1 represents the state of a session key. diff --git a/pkg/core/session_key_test.go b/pkg/core/session_key_test.go index f7d89442e..c21d999d1 100644 --- a/pkg/core/session_key_test.go +++ b/pkg/core/session_key_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/core/types.go b/pkg/core/types.go index 332f93ab8..b1e815417 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -2,6 +2,8 @@ package core import ( "fmt" + "strconv" + "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -46,6 +48,47 @@ func (s ChannelStatus) String() string { } } +func (s *ChannelStatus) Scan(src any) error { + switch v := src.(type) { + case int64: + *s = ChannelStatus(uint8(v)) + return nil + case int32: + *s = ChannelStatus(uint8(v)) + return nil + case int: + *s = ChannelStatus(uint8(v)) + return nil + case string: + return s.scanString(v) + default: + return fmt.Errorf("unsupported ChannelStatus scan type %T", src) + } +} + +func (s *ChannelStatus) scanString(v string) error { + v = strings.TrimSpace(v) + // if numeric + if n, err := strconv.Atoi(v); err == nil { + *s = ChannelStatus(uint8(n)) + return nil + } + // else map names + switch strings.ToLower(v) { + case ChannelStatusVoid.String(): + *s = ChannelStatusVoid + case ChannelStatusOpen.String(): + *s = ChannelStatusOpen + case ChannelStatusChallenged.String(): + *s = ChannelStatusChallenged + case ChannelStatusClosed.String(): + *s = ChannelStatusClosed + default: + return fmt.Errorf("unknown ChannelStatus %q", v) + } + return nil +} + const ( INTENT_OPERATE = 0 INTENT_CLOSE = 1 @@ -842,6 +885,15 @@ func (t TransitionType) String() string { } } +func (t TransitionType) GatedAction() GatedAction { + switch t { + case TransitionTypeTransferSend: + return GatedActionTransfer + default: + return "" + } +} + // Transition represents a state transition type Transition struct { Type TransitionType `json:"type"` // Type of state transition @@ -879,10 +931,11 @@ func (t1 Transition) Equal(t2 Transition) error { // Blockchain represents information about a supported blockchain network type Blockchain struct { - Name string `json:"name"` // Blockchain name - ID uint64 `json:"id"` // Blockchain network ID - ChannelHubAddress string `json:"channel_hub_address"` // Address of the ChannelHub contract on this blockchain - BlockStep uint64 `json:"block_step"` // Number of blocks between each channel update + Name string `json:"name"` // Blockchain name + ID uint64 `json:"id"` // Blockchain network ID + ChannelHubAddress string `json:"channel_hub_address"` // Address of the ChannelHub contract on this blockchain + LockingContractAddress string `json:"locking_contract_address"` // Address of the Locking contract on this blockchain + BlockStep uint64 `json:"block_step"` // Number of blocks between each channel update } // Asset represents information about a supported asset @@ -903,22 +956,61 @@ type Token struct { Decimals uint8 `json:"decimals"` // Number of decimal places } -// SessionKey represents a session key with spending allowances -type SessionKey struct { - ID uint64 `json:"id"` // Unique identifier for the session key record - SessionKey string `json:"session_key"` // The address of the session key - Application string `json:"application"` // Name of the application authorized for this session key - Allowances []AssetAllowance `json:"allowances"` // Asset allowances with usage tracking - Scope *string `json:"scope,omitempty"` // Permission scope for this session key - ExpiresAt string `json:"expires_at"` // When this session key expires (ISO 8601 format) - CreatedAt string `json:"created_at"` // When the session key was created (ISO 8601 format) +// GatedAction represents an action that can be gated behind certain conditions, such as feature flags or access controls. +type GatedAction string + +var ( + GatedActionTransfer GatedAction = "transfer" + + GatedActionAppSessionCreation GatedAction = "app_session_creation" + GatedActionAppSessionOperation GatedAction = "app_session_operation" + GatedActionAppSessionDeposit GatedAction = "app_session_deposit" + GatedActionAppSessionWithdrawal GatedAction = "app_session_withdrawal" +) + +// ID returns a unique identifier for the GatedAction, which can be used for efficient storage and retrieval in databases or feature flag systems. +func (g GatedAction) ID() uint8 { + switch g { + case GatedActionTransfer: + return 1 + case GatedActionAppSessionCreation: + return 10 + case GatedActionAppSessionOperation: + return 11 + case GatedActionAppSessionDeposit: + return 12 + case GatedActionAppSessionWithdrawal: + return 13 + } + return 0 +} + +// GatedActionFromID returns the GatedAction corresponding to the given uint8 ID. +// Returns an empty GatedAction and false if the ID is unknown. +func GatedActionFromID(id uint8) (GatedAction, bool) { + switch id { + case 1: + return GatedActionTransfer, true + case 10: + return GatedActionAppSessionCreation, true + case 11: + return GatedActionAppSessionOperation, true + case 12: + return GatedActionAppSessionDeposit, true + case 13: + return GatedActionAppSessionWithdrawal, true + default: + return "", false + } } -// AssetAllowance represents asset allowance with usage tracking -type AssetAllowance struct { - Asset string `json:"asset"` // Symbol of the asset - Allowance decimal.Decimal `json:"allowance"` // Maximum amount the session key can spend - Used decimal.Decimal `json:"used"` // Amount already spent by this session key +// ActionAllowance represents the allowance information for a specific gated action, +// including the time window for which the allowance applies, the total allowance, and the amount used. +type ActionAllowance struct { + GatedAction GatedAction + TimeWindow string + Allowance uint64 + Used uint64 } // ========= Blockchain CLient Response Types ========= diff --git a/pkg/log/context_test.go b/pkg/log/context_test.go index a0ef839fc..ec40058e1 100644 --- a/pkg/log/context_test.go +++ b/pkg/log/context_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/log" ) // TestContextLogger tests the context-based logger functionality. diff --git a/pkg/log/mock_logger_test.go b/pkg/log/mock_logger_test.go index 4b5e0b7d0..35b12a72d 100644 --- a/pkg/log/mock_logger_test.go +++ b/pkg/log/mock_logger_test.go @@ -1,6 +1,6 @@ package log_test -import "github.com/erc7824/nitrolite/pkg/log" +import "github.com/layer-3/nitrolite/pkg/log" var _ log.Logger = &MockLogger{} diff --git a/pkg/log/span_logger_test.go b/pkg/log/span_logger_test.go index 1cdc0850c..37d42d618 100644 --- a/pkg/log/span_logger_test.go +++ b/pkg/log/span_logger_test.go @@ -3,7 +3,7 @@ package log_test import ( "testing" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/log" "github.com/stretchr/testify/assert" ) diff --git a/pkg/log/zap_logger_test.go b/pkg/log/zap_logger_test.go index 6a70c6a95..37d46da77 100644 --- a/pkg/log/zap_logger_test.go +++ b/pkg/log/zap_logger_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/log" ) // TestZapLogger comprehensively tests the ZapLogger implementation. diff --git a/pkg/rpc/README.md b/pkg/rpc/README.md index c0a6bbb0f..642b29ac6 100644 --- a/pkg/rpc/README.md +++ b/pkg/rpc/README.md @@ -117,7 +117,7 @@ if err := db.Save(); err != nil { ## Installation ```go -import "github.com/erc7824/nitrolite/pkg/rpc" +import "github.com/layer-3/nitrolite/pkg/rpc" ``` ## Server Usage @@ -126,8 +126,8 @@ import "github.com/erc7824/nitrolite/pkg/rpc" ```go import ( - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/log" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/log" ) // Create server configuration @@ -255,7 +255,7 @@ func rateLimitMiddleware(c *rpc.Context) { ### Quick Start ```go -import "github.com/erc7824/nitrolite/pkg/rpc" +import "github.com/layer-3/nitrolite/pkg/rpc" // Create client dialer := rpc.NewWebsocketDialer(rpc.DefaultWebsocketDialerConfig) @@ -273,7 +273,7 @@ go func() { // Connect to server ctx := context.Background() -err := client.Start(ctx, "wss://node.example.com/ws", func(err error) { +err := client.Start(ctx, "wss://clearnode-sandbox.yellow.org/v1/ws", func(err error) { if err != nil { log.Error("Connection closed", "error", err) } @@ -597,4 +597,4 @@ Test coverage includes: ## See Also - [API Documentation](api.yaml) - OpenAPI specification for V1 API -- Package documentation: `go doc github.com/erc7824/nitrolite/pkg/rpc` +- Package documentation: `go doc github.com/layer-3/nitrolite/pkg/rpc` diff --git a/pkg/rpc/api.go b/pkg/rpc/api.go index d22fd2b20..72b364191 100644 --- a/pkg/rpc/api.go +++ b/pkg/rpc/api.go @@ -6,7 +6,7 @@ package rpc import ( - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // ============================================================================ @@ -247,8 +247,10 @@ type AppSessionsV1CreateAppSessionRequest struct { Definition AppDefinitionV1 `json:"definition"` // SessionData is the optional JSON stringified session data SessionData string `json:"session_data"` - - QuorumSigs []string `json:"quorum_sigs,omitempty"` // Participant signatures for the app session creation + // QuorumSigs is the list of participant signatures for the app session creation + QuorumSigs []string `json:"quorum_sigs,omitempty"` + // OwnerSig is the optional owner signature for app session creation if approval required by the app registry + OwnerSig string `json:"owner_sig,omitempty"` } // AppSessionsV1CreateAppSessionResponse returns the created application session information. @@ -284,6 +286,40 @@ type AppSessionsV1GetLastKeyStatesResponse struct { States []AppSessionKeyStateV1 `json:"states"` } +// ============================================================================ +// Apps Group - V1 API +// ============================================================================ + +// AppsV1GetAppsRequest retrieves registered applications with optional filtering. +type AppsV1GetAppsRequest struct { + // AppID filters by application ID + AppID *string `json:"app_id,omitempty"` + // OwnerWallet filters by owner wallet address + OwnerWallet *string `json:"owner_wallet,omitempty"` + // Pagination contains pagination parameters (offset, limit, sort) + Pagination *PaginationParamsV1 `json:"pagination,omitempty"` +} + +// AppsV1GetAppsResponse returns the list of registered applications. +type AppsV1GetAppsResponse struct { + // Apps is the list of registered applications + Apps []AppInfoV1 `json:"apps"` + // Metadata contains pagination information + Metadata PaginationMetadataV1 `json:"metadata"` +} + +// AppsV1SubmitAppVersionRequest submits a new application version (currently only creation is supported). +type AppsV1SubmitAppVersionRequest struct { + // App contains the application definition + App AppV1 `json:"app"` + // OwnerSig is the owner's signature over the packed app data + OwnerSig string `json:"owner_sig"` +} + +// AppsV1SubmitAppVersionResponse returns the result of the application version submission. +type AppsV1SubmitAppVersionResponse struct { +} + // ============================================================================ // User Group - V1 API // ============================================================================ @@ -324,6 +360,18 @@ type UserV1GetTransactionsResponse struct { Metadata PaginationMetadataV1 `json:"metadata"` } +// UserV1GetActionAllowancesRequest retrieves the current action allowances for a user. +type UserV1GetActionAllowancesRequest struct { + // Wallet is the user's wallet address + Wallet string `json:"wallet"` +} + +// UserV1GetActionAllowancesResponse returns the list of action allowances for the user. +type UserV1GetActionAllowancesResponse struct { + // Allowances is the list of action allowances for the user + Allowances []ActionAllowanceV1 `json:"allowances"` +} + // ============================================================================ // Node Group - V1 API // ============================================================================ diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 5a26261a8..f479d0da6 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -28,7 +28,7 @@ import ( // client := rpc.NewClient(dialer) // // // Connect to the server -// err := client.Start(ctx, "wss://server.example.com/ws", handleError) +// err := client.Start(ctx, "wss://clearnode-sandbox.yellow.org/v1/ws", handleError) // if err != nil { // log.Fatal(err) // } @@ -217,6 +217,28 @@ func (c *Client) AppSessionsV1GetLastKeyStates(ctx context.Context, req AppSessi return resp, nil } +// ============================================================================ +// Apps Group - V1 API Methods +// ============================================================================ + +// AppsV1GetApps retrieves registered applications with optional filtering. +func (c *Client) AppsV1GetApps(ctx context.Context, req AppsV1GetAppsRequest) (AppsV1GetAppsResponse, error) { + var resp AppsV1GetAppsResponse + if err := c.call(ctx, AppsV1GetAppsMethod, req, &resp); err != nil { + return resp, err + } + return resp, nil +} + +// AppsV1SubmitAppVersion submits a new application version (currently only creation is supported). +func (c *Client) AppsV1SubmitAppVersion(ctx context.Context, req AppsV1SubmitAppVersionRequest) (AppsV1SubmitAppVersionResponse, error) { + var resp AppsV1SubmitAppVersionResponse + if err := c.call(ctx, AppsV1SubmitAppVersionMethod, req, &resp); err != nil { + return resp, err + } + return resp, nil +} + // ============================================================================ // User Group - V1 API Methods // ============================================================================ @@ -239,6 +261,15 @@ func (c *Client) UserV1GetTransactions(ctx context.Context, req UserV1GetTransac return resp, nil } +// UserV1GetActionAllowances retrieves the user's current action allowances for channels and app sessions. +func (c *Client) UserV1GetActionAllowances(ctx context.Context, req UserV1GetActionAllowancesRequest) (UserV1GetActionAllowancesResponse, error) { + var resp UserV1GetActionAllowancesResponse + if err := c.call(ctx, UserV1GetActionAllowancesMethod, req, &resp); err != nil { + return resp, err + } + return resp, nil +} + // ============================================================================ // Node Group - V1 API Methods // ============================================================================ diff --git a/pkg/rpc/client_test.go b/pkg/rpc/client_test.go index 3bbc44266..658e35e1e 100644 --- a/pkg/rpc/client_test.go +++ b/pkg/rpc/client_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/rpc" ) // Test helpers for V1 client @@ -362,12 +362,15 @@ func TestClientV1_AppSessionsV1GetAppSessions(t *testing.T) { { AppSessionID: testAppSession, Status: "open", - Participants: []rpc.AppParticipantV1{ - {WalletAddress: testWalletV1, SignatureWeight: 1}, + AppDefinitionV1: rpc.AppDefinitionV1{ + Application: "0xapp", + Participants: []rpc.AppParticipantV1{ + {WalletAddress: testWalletV1, SignatureWeight: 1}, + }, + Nonce: "1", + Quorum: 1, }, - Quorum: 1, Version: "1", - Nonce: "1", }, }, Metadata: rpc.PaginationMetadataV1{ @@ -509,6 +512,133 @@ func TestClientV1_AppSessionsV1GetLastKeyStates(t *testing.T) { assert.Equal(t, "0xsession_key_1", res.States[0].SessionKey) } +// ============================================================================ +// Apps Group Tests +// ============================================================================ + +func TestClientV1_AppsV1GetApps(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + response := rpc.AppsV1GetAppsResponse{ + Apps: []rpc.AppInfoV1{ + { + AppV1: rpc.AppV1{ + ID: "my-app", + OwnerWallet: testWalletV1, + Metadata: `{"name": "My App"}`, + Version: "1", + CreationApprovalNotRequired: true, + }, + CreatedAt: "1700000000", + UpdatedAt: "1700000001", + }, + { + AppV1: rpc.AppV1{ + ID: "another-app", + OwnerWallet: testWallet2V1, + Metadata: `{"name": "Another App"}`, + Version: "1", + CreationApprovalNotRequired: false, + }, + CreatedAt: "1700000100", + UpdatedAt: "1700000200", + }, + }, + Metadata: rpc.PaginationMetadataV1{ + Page: 1, + PerPage: 10, + TotalCount: 2, + PageCount: 1, + }, + } + + registerSimpleHandlerV1(dialer, rpc.AppsV1GetAppsMethod.String(), response) + + resp, err := client.AppsV1GetApps(testCtxV1, rpc.AppsV1GetAppsRequest{}) + require.NoError(t, err) + assert.Len(t, resp.Apps, 2) + assert.Equal(t, "my-app", resp.Apps[0].ID) + assert.Equal(t, testWalletV1, resp.Apps[0].OwnerWallet) + assert.Equal(t, `{"name": "My App"}`, resp.Apps[0].Metadata) + assert.Equal(t, "1", resp.Apps[0].Version) + assert.True(t, resp.Apps[0].CreationApprovalNotRequired) + assert.Equal(t, "1700000000", resp.Apps[0].CreatedAt) + assert.Equal(t, "1700000001", resp.Apps[0].UpdatedAt) + assert.Equal(t, uint32(2), resp.Metadata.TotalCount) +} + +func TestClientV1_AppsV1GetApps_WithFilters(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + appID := "my-app" + ownerWallet := testWalletV1 + + response := rpc.AppsV1GetAppsResponse{ + Apps: []rpc.AppInfoV1{ + { + AppV1: rpc.AppV1{ + ID: appID, + OwnerWallet: ownerWallet, + Metadata: `{}`, + Version: "1", + }, + CreatedAt: "1700000000", + UpdatedAt: "1700000000", + }, + }, + Metadata: rpc.PaginationMetadataV1{ + Page: 1, + PerPage: 10, + TotalCount: 1, + PageCount: 1, + }, + } + + registerSimpleHandlerV1(dialer, rpc.AppsV1GetAppsMethod.String(), response) + + resp, err := client.AppsV1GetApps(testCtxV1, rpc.AppsV1GetAppsRequest{ + AppID: &appID, + OwnerWallet: &ownerWallet, + Pagination: &rpc.PaginationParamsV1{ + Offset: ptrUint32(0), + Limit: ptrUint32(10), + }, + }) + require.NoError(t, err) + assert.Len(t, resp.Apps, 1) + assert.Equal(t, appID, resp.Apps[0].ID) +} + +func TestClientV1_AppsV1SubmitAppVersion(t *testing.T) { + t.Parallel() + + client, dialer := setupClient() + + response := rpc.AppsV1SubmitAppVersionResponse{} + + registerSimpleHandlerV1(dialer, rpc.AppsV1SubmitAppVersionMethod.String(), response) + + _, err := client.AppsV1SubmitAppVersion(testCtxV1, rpc.AppsV1SubmitAppVersionRequest{ + App: rpc.AppV1{ + ID: "my-app", + OwnerWallet: testWalletV1, + Metadata: `{"name": "My App"}`, + Version: "1", + CreationApprovalNotRequired: false, + }, + OwnerSig: "0xsig123", + }) + require.NoError(t, err) +} + +// ============================================================================ +// User Group Tests +// ============================================================================ + func TestClientV1_UserV1GetBalances(t *testing.T) { t.Parallel() @@ -587,3 +717,11 @@ func TestClientV1_ErrorHandling(t *testing.T) { _, err = client.NodeV1GetAssets(testCtxV1, rpc.NodeV1GetAssetsRequest{}) assert.Contains(t, err.Error(), "internal server error") } + +// ============================================================================ +// Test Helpers +// ============================================================================ + +func ptrUint32(v uint32) *uint32 { + return &v +} diff --git a/pkg/rpc/connection.go b/pkg/rpc/connection.go index c943d0044..273e01701 100644 --- a/pkg/rpc/connection.go +++ b/pkg/rpc/connection.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/erc7824/nitrolite/pkg/log" "github.com/gorilla/websocket" + "github.com/layer-3/nitrolite/pkg/log" ) // Default values are carefully chosen to balance resource consumption and operational flexibility. diff --git a/pkg/rpc/connection_test.go b/pkg/rpc/connection_test.go index 6e45c2559..294073dc7 100644 --- a/pkg/rpc/connection_test.go +++ b/pkg/rpc/connection_test.go @@ -10,7 +10,7 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) func TestNewWebsocketConnection(t *testing.T) { diff --git a/pkg/rpc/dialer.go b/pkg/rpc/dialer.go index 118d98b7e..9adaf61f7 100644 --- a/pkg/rpc/dialer.go +++ b/pkg/rpc/dialer.go @@ -8,8 +8,8 @@ import ( "sync" "time" - "github.com/erc7824/nitrolite/pkg/log" "github.com/gorilla/websocket" + "github.com/layer-3/nitrolite/pkg/log" ) // Dialer is the interface for RPC client connections. diff --git a/pkg/rpc/dialer_test.go b/pkg/rpc/dialer_test.go index c28d9a554..20df92851 100644 --- a/pkg/rpc/dialer_test.go +++ b/pkg/rpc/dialer_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/rpc" "github.com/gorilla/websocket" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/rpc/doc.go b/pkg/rpc/doc.go index c565b834c..2c0683da4 100644 --- a/pkg/rpc/doc.go +++ b/pkg/rpc/doc.go @@ -132,7 +132,7 @@ // client := rpc.NewClient(dialer) // // // Connect to server -// err := client.Start(ctx, "wss://server.example.com/ws", func(err error) { +// err := client.Start(ctx, "wss://clearnode-sandbox.yellow.org/v1/ws", func(err error) { // if err != nil { // log.Error("Connection closed", "error", err) // } @@ -346,5 +346,5 @@ // // Run tests with: // -// go test github.com/erc7824/nitrolite/pkg/rpc +// go test github.com/layer-3/nitrolite/pkg/rpc package rpc diff --git a/pkg/rpc/error.go b/pkg/rpc/error.go index cd18473ef..50aa885ca 100644 --- a/pkg/rpc/error.go +++ b/pkg/rpc/error.go @@ -49,6 +49,24 @@ type Error struct { err error } +// NewError creates a new Error from an existing error. The message from the provided error will be included in the RPC response sent to the client. +// +// Use this function when you have an existing error that contains a user-friendly message +// that you want to send back to the client. If the original error message is not suitable +// for client consumption, consider using Errorf to create a new Error with a custom message. +// +// Example: +// +// // Creating an Error from an existing error +// if err := validateAddress(addr); err != nil { +// return rpc.NewError(err) +// } +// +// // This will send the original error message to the client, so ensure it's appropriate for exposure. +func NewError(err error) Error { + return Error{err: err} +} + // Errorf creates a new Error with a formatted error message that will be sent // to the client in the RPC response. This is the preferred way to create client-facing // errors in RPC handlers. diff --git a/pkg/rpc/methods.go b/pkg/rpc/methods.go index 9b3cf44c9..de8a37df6 100644 --- a/pkg/rpc/methods.go +++ b/pkg/rpc/methods.go @@ -29,10 +29,16 @@ const ( AppSessionsV1SubmitSessionKeyStateMethod Method = "app_sessions.v1.submit_session_key_state" AppSessionsV1GetLastKeyStatesMethod Method = "app_sessions.v1.get_last_key_states" + // Apps Group - V1 Methods + AppsV1Group Group = "apps.v1" + AppsV1GetAppsMethod Method = "apps.v1.get_apps" + AppsV1SubmitAppVersionMethod Method = "apps.v1.submit_app_version" + // User Group - V1 Methods - UserV1Group Group = "user.v1" - UserV1GetBalancesMethod Method = "user.v1.get_balances" - UserV1GetTransactionsMethod Method = "user.v1.get_transactions" + UserV1Group Group = "user.v1" + UserV1GetBalancesMethod Method = "user.v1.get_balances" + UserV1GetTransactionsMethod Method = "user.v1.get_transactions" + UserV1GetActionAllowancesMethod Method = "user.v1.get_action_allowances" // Node Group - V1 Methods NodeV1Group Group = "node.v1" diff --git a/pkg/rpc/mock_dialer_test.go b/pkg/rpc/mock_dialer_test.go index b19771fbf..b56a3b1c4 100644 --- a/pkg/rpc/mock_dialer_test.go +++ b/pkg/rpc/mock_dialer_test.go @@ -3,7 +3,7 @@ package rpc_test import ( "context" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) // MockCallHandler is a function type that handles RPC calls in the mock dialer. diff --git a/pkg/rpc/node.go b/pkg/rpc/node.go index c1a78c1b8..10202c559 100644 --- a/pkg/rpc/node.go +++ b/pkg/rpc/node.go @@ -8,9 +8,9 @@ import ( "sync" "time" - "github.com/erc7824/nitrolite/pkg/log" "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/layer-3/nitrolite/pkg/log" ) const ( @@ -212,10 +212,12 @@ func (wn *WebsocketNode) ServeHTTP(w http.ResponseWriter, r *http.Request) { connectionID := uuid.NewString() connConfig := WebsocketConnectionConfig{ - ConnectionID: connectionID, - Origin: r.Header.Get("Origin"), - WebsocketConn: wsConnection, - Logger: wn.cfg.Logger, + ConnectionID: connectionID, + Origin: r.Header.Get("Origin"), + WebsocketConn: wsConnection, + Logger: wn.cfg.Logger, + ProcessBufferSize: wn.cfg.WsConnProcessBufferSize, + WriteBufferSize: wn.cfg.WsConnWriteBufferSize, } connection, err := NewWebsocketConnection(connConfig) if err != nil { diff --git a/pkg/rpc/payload_test.go b/pkg/rpc/payload_test.go index 3de85421c..3447f71f7 100644 --- a/pkg/rpc/payload_test.go +++ b/pkg/rpc/payload_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go index 87b914994..31826af3f 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/types.go @@ -6,8 +6,8 @@ package rpc import ( "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" ) // ============================================================================ @@ -148,7 +148,7 @@ type AppParticipantV1 struct { // AppDefinitionV1 represents the definition for an app session. type AppDefinitionV1 struct { // Application is the application identifier from an app registry - Application string `json:"application"` + Application string `json:"application_id"` // Participants is the list of participants in the app session Participants []AppParticipantV1 `json:"participants"` // Quorum is the quorum required for the app session @@ -187,17 +187,13 @@ type AppSessionInfoV1 struct { AppSessionID string `json:"app_session_id"` // Status is the session status (open/closed) Status string `json:"status"` - // Participants is the list of participant wallet addresses with weights - Participants []AppParticipantV1 `json:"participants"` + // AppDefinitionV1 contains immutable application fields + AppDefinitionV1 AppDefinitionV1 `json:"app_definition"` // SessionData is the JSON stringified session data SessionData *string `json:"session_data,omitempty"` - // Quorum is the quorum required for operations - Quorum uint8 `json:"quorum"` // Version is the current version of the session state Version string `json:"version"` // Nonce is the nonce for the session - Nonce string `json:"nonce"` - // Allocations is the list of allocations in the app state Allocations []AppAllocationV1 `json:"allocations"` } @@ -220,6 +216,33 @@ type AppSessionKeyStateV1 struct { UserSig string `json:"user_sig"` } +// ============================================================================ +// App Registry Types +// ============================================================================ + +// AppV1 represents a registered application definition (without timestamps). +type AppV1 struct { + // ID is the application identifier + ID string `json:"id"` + // OwnerWallet is the owner's wallet address + OwnerWallet string `json:"owner_wallet"` + // Metadata is the application metadata + Metadata string `json:"metadata"` + // Version is the current version + Version string `json:"version"` + // CreationApprovalNotRequired indicates if sessions can be created without approval + CreationApprovalNotRequired bool `json:"creation_approval_not_required"` +} + +// AppInfoV1 represents full application info including timestamps. +type AppInfoV1 struct { + AppV1 + // CreatedAt is the creation timestamp (unix seconds) + CreatedAt string `json:"created_at"` + // UpdatedAt is the last update timestamp (unix seconds) + UpdatedAt string `json:"updated_at"` +} + // ============================================================================ // Asset and Blockchain Types // ============================================================================ @@ -260,7 +283,8 @@ type BlockchainInfoV1 struct { BlockchainID string `json:"blockchain_id"` // ChannelHubAddress is the contract address on this network ChannelHubAddress string `json:"channel_hub_address"` - // DefaultValidatorAddress is the validator contract address set in a channel supported by clearnode + // LockingContractAddress is the contract address for the locking contract on this network + LockingContractAddress string `json:"locking_contract_address"` } // ============================================================================ @@ -297,6 +321,22 @@ type TransactionV1 struct { CreatedAt string `json:"created_at"` } +// ============================================================================ +// Action Gateway Types +// ============================================================================ + +// ActionAllowanceV1 represents the allowance information for a specific gated action. +type ActionAllowanceV1 struct { + // GatedAction is the specific action being gated (e.g. transfer, app_operation) + GatedAction core.GatedAction `json:"gated_action"` + // TimeWindow is the time window for which the allowance is valid (e.g. "1h", "24h") + TimeWindow string `json:"time_window"` + // Allowance is the total allowance for the gated action within the time window + Allowance string `json:"allowance"` + // Used is the amount of the allowance that has already been used within the time window + Used string `json:"used"` +} + // ============================================================================ // Pagination Types // ============================================================================ diff --git a/pkg/sign/README.md b/pkg/sign/README.md index 09eb52ae5..07a1a919e 100644 --- a/pkg/sign/README.md +++ b/pkg/sign/README.md @@ -17,4 +17,4 @@ This library separates generic interfaces from specific blockchain implementatio See the Go package documentation and examples by running: ```bash -go doc -all github.com/erc7824/nitrolite/pkg/sign \ No newline at end of file +go doc -all github.com/layer-3/nitrolite/pkg/sign \ No newline at end of file diff --git a/pkg/sign/example_test.go b/pkg/sign/example_test.go index f36ecdaa3..775dfc33c 100644 --- a/pkg/sign/example_test.go +++ b/pkg/sign/example_test.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/erc7824/nitrolite/pkg/sign" ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/sign" ) // ExampleNewEthereumRawSigner demonstrates creating an Ethereum signer and signing a message. diff --git a/pkg/sign/kms/gcp/gcp_test.go b/pkg/sign/kms/gcp/gcp_test.go index b55f80cc2..4606e4a19 100644 --- a/pkg/sign/kms/gcp/gcp_test.go +++ b/pkg/sign/kms/gcp/gcp_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/sign" ) const envKMSKeyName = "GCP_KMS_KEY_NAME" diff --git a/pkg/sign/kms/gcp/signer.go b/pkg/sign/kms/gcp/signer.go index 439585299..1ac9699fd 100644 --- a/pkg/sign/kms/gcp/signer.go +++ b/pkg/sign/kms/gcp/signer.go @@ -21,8 +21,8 @@ import ( "github.com/ethereum/go-ethereum/crypto/secp256k1" - "github.com/erc7824/nitrolite/pkg/sign" - kmssign "github.com/erc7824/nitrolite/pkg/sign/kms" + "github.com/layer-3/nitrolite/pkg/sign" + kmssign "github.com/layer-3/nitrolite/pkg/sign/kms" ) // crc32cTable is pre-computed once to avoid re-creating it on every Sign() call. diff --git a/pkg/sign/kms/gcp/signer_test.go b/pkg/sign/kms/gcp/signer_test.go index f373ebc40..98981803a 100644 --- a/pkg/sign/kms/gcp/signer_test.go +++ b/pkg/sign/kms/gcp/signer_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/erc7824/nitrolite/pkg/sign" + "github.com/layer-3/nitrolite/pkg/sign" ) // buildSecp256k1PEM creates a PEM-encoded SubjectPublicKeyInfo for a secp256k1 key, diff --git a/sdk/go/README.md b/sdk/go/README.md index 2a229b2eb..c20acc395 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -1,4 +1,4 @@ -[![Go Reference](https://pkg.go.dev/badge/github.com/erc7824/nitrolite/sdk/go.svg)](https://pkg.go.dev/github.com/erc7824/nitrolite/sdk/go) +[![Go Reference](https://pkg.go.dev/badge/github.com/layer-3/nitrolite/sdk/go.svg)](https://pkg.go.dev/github.com/layer-3/nitrolite/sdk/go) # Clearnode Go SDK @@ -47,11 +47,18 @@ client.GetEscrowChannel(ctx, escrowChannelID) // Escrow channel info client.GetLatestState(ctx, wallet, asset, onlySigned) // Latest state ``` +### App Registry +```go +client.GetApps(ctx, opts) // List registered apps +client.RegisterApp(ctx, appID, metadata, approvalNotRequired) // Register new app +``` + ### App Sessions ```go client.GetAppSessions(ctx, opts) // List sessions client.GetAppDefinition(ctx, appSessionID) // Session definition client.CreateAppSession(ctx, definition, sessionData, sigs) // Create session +client.CreateAppSession(ctx, def, data, sigs, opts) // Create with owner approval client.SubmitAppSessionDeposit(ctx, update, sigs, asset, amount) // Deposit to session client.SubmitAppState(ctx, update, sigs) // Update session client.RebalanceAppSessions(ctx, signedUpdates) // Atomic rebalance @@ -89,9 +96,9 @@ package main import ( "context" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" "github.com/shopspring/decimal" ) @@ -103,7 +110,7 @@ func main() { // Create unified client client, _ := sdk.NewClient( - "wss://clearnode.example.com/ws", + "wss://clearnode-sandbox.yellow.org/v1/ws", stateSigner, txSigner, sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -137,6 +144,7 @@ sdk/go/ ├── node.go # Node information methods ├── user.go # User query methods ├── channel.go # Channel and state management +├── app_registry.go # App registry methods ├── app_session.go # App session methods ├── asset_cache.go # Asset lookup and caching ├── config.go # Configuration options @@ -351,6 +359,19 @@ state, err := client.GetLatestState(ctx, wallet, asset, onlySigned) **Note:** State submission and channel creation are handled internally by state operations (Deposit, Withdraw, Transfer). On-chain settlement is handled by Checkpoint. +### App Registry + +```go +// List registered applications with optional filtering +apps, meta, err := client.GetApps(ctx, &sdk.GetAppsOptions{ + AppID: &appID, + OwnerWallet: &wallet, +}) + +// Register a new application +err := client.RegisterApp(ctx, "my-app", `{"name": "My App"}`, false) +``` + ### App Sessions (Low-Level) ```go @@ -362,6 +383,37 @@ err := client.SubmitAppState(ctx, update, sigs) batchID, err := client.RebalanceAppSessions(ctx, signedUpdates) ``` +#### Owner Approval for App Session Creation + +When an app is registered with `creationApprovalNotRequired: false`, the app owner must sign the session creation request. Pass the owner's signature via `CreateAppSessionOptions`: + +```go +// Owner signs the create request using their app session signer +ownerSig, _ := ownerAppSessionSigner.Sign(createRequest) + +sessionID, _, _, err := client.CreateAppSession(ctx, def, data, sigs, + sdk.CreateAppSessionOptions{OwnerSig: ownerSig.String()}, +) +``` + +### App Session Signers (`pkg/app`) + +App session operations require signatures with a type byte prefix, similar to channel signers: + +| Type | Byte | Constructor | Usage | +|------|------|------------|-------| +| Wallet | `0xA1` | `app.NewAppSessionWalletSignerV1(msgSigner)` | Main wallet signs app session operations | +| Session Key | `0xA2` | `app.NewAppSessionKeySignerV1(msgSigner)` | Delegated session key signs on behalf of wallet | + +```go +// Create app session wallet signer +msgSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex) +appSessionSigner, _ := app.NewAppSessionWalletSignerV1(msgSigner) + +// Sign app session operations (create, deposit, state updates, etc.) +sig, _ := appSessionSigner.Sign(packedRequest) +``` + ### Session Keys — App Sessions ```go @@ -558,12 +610,15 @@ go run lifecycle.go ``` This example demonstrates: -- Creating app sessions with multiple participants +- Registering apps in the app registry (with and without owner approval) +- Creating app sessions with single and multiple participants +- Owner approval for app session creation +- Session key delegation for app session participants - Depositing assets into app sessions - Operating on app session state (redistributing allocations) -- Atomic rebalancing across multiple app sessions - Withdrawing from app sessions - Closing app sessions +- Fail case: attempting to create a session for an unregistered app The example walks through a complete multi-party app session scenario with three wallets. @@ -586,6 +641,10 @@ core.ChannelSessionKeyStateV1 // Channel session key state // Fields: UserAddress, SessionKey, Version (uint64), Assets []string, // ExpiresAt (time.Time), UserSig string +// App registry types +app.AppV1 // Application definition +app.AppInfoV1 // Application info with timestamps + // App session types app.AppSessionInfoV1 // Session info app.AppDefinitionV1 // Session definition diff --git a/sdk/go/app_registry.go b/sdk/go/app_registry.go new file mode 100644 index 000000000..baa3e8e6f --- /dev/null +++ b/sdk/go/app_registry.go @@ -0,0 +1,170 @@ +package sdk + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" +) + +// ============================================================================ +// App Registry Methods +// ============================================================================ + +// GetAppsOptions contains optional filters for GetApps. +type GetAppsOptions struct { + // AppID filters by application ID + AppID *string + + // OwnerWallet filters by owner wallet address + OwnerWallet *string + + // Pagination parameters + Pagination *core.PaginationParams +} + +// GetApps retrieves registered applications with optional filtering. +// +// Parameters: +// - opts: Optional filters (pass nil for no filters) +// +// Returns: +// - Slice of AppInfoV1 with application information +// - core.PaginationMetadata with pagination information +// - Error if the request fails +// +// Example: +// +// apps, meta, err := client.GetApps(ctx, nil) +// for _, a := range apps { +// fmt.Printf("App %s owned by %s\n", a.App.ID, a.App.OwnerWallet) +// } +func (c *Client) GetApps(ctx context.Context, opts *GetAppsOptions) ([]app.AppInfoV1, core.PaginationMetadata, error) { + req := rpc.AppsV1GetAppsRequest{} + if opts != nil { + req.AppID = opts.AppID + req.OwnerWallet = opts.OwnerWallet + req.Pagination = transformPaginationParams(opts.Pagination) + } + resp, err := c.rpcClient.AppsV1GetApps(ctx, req) + if err != nil { + return nil, core.PaginationMetadata{}, fmt.Errorf("failed to get apps: %w", err) + } + + apps, err := transformApps(resp.Apps) + if err != nil { + return nil, core.PaginationMetadata{}, fmt.Errorf("failed to transform apps: %w", err) + } + + return apps, transformPaginationMetadata(resp.Metadata), nil +} + +// RegisterApp registers a new application in the app registry. +// Currently only version 1 (creation) is supported. +// +// The method builds the app definition from the provided parameters, +// using the client's signer address as the owner wallet and version 1. +// It then packs and signs the definition automatically. +// +// Session key signers are not allowed to perform this action; the main +// wallet signer must be used. +// +// Parameters: +// - appID: The application identifier +// - metadata: The application metadata +// - creationApprovalNotRequired: Whether sessions can be created without owner approval +// +// Returns: +// - Error if the request fails +// +// Example: +// +// err := client.RegisterApp(ctx, "my-app", `{"name": "My App"}`, false) +func (c *Client) RegisterApp(ctx context.Context, appID string, metadata string, creationApprovalNotRequired bool) error { + appDef := app.AppV1{ + ID: appID, + OwnerWallet: c.GetUserAddress(), + Metadata: metadata, + Version: 1, + CreationApprovalNotRequired: creationApprovalNotRequired, + } + + packed, err := app.PackAppV1(appDef) + if err != nil { + return fmt.Errorf("failed to pack app: %w", err) + } + + ethMsgSigner, err := sign.NewEthereumMsgSignerFromRaw(c.rawSigner) + if err != nil { + return fmt.Errorf("failed to create Ethereum message signer: %w", err) + } + + sig, err := ethMsgSigner.Sign(packed) + if err != nil { + return fmt.Errorf("failed to sign app data: %w", err) + } + + req := rpc.AppsV1SubmitAppVersionRequest{ + App: transformAppToRPC(appDef), + OwnerSig: sig.String(), + } + _, err = c.rpcClient.AppsV1SubmitAppVersion(ctx, req) + if err != nil { + return fmt.Errorf("failed to register app: %w", err) + } + return nil +} + +// ============================================================================ +// App Registry Transformations +// ============================================================================ + +// transformApps converts RPC AppInfoV1 slice to app.AppInfoV1 slice. +func transformApps(apps []rpc.AppInfoV1) ([]app.AppInfoV1, error) { + result := make([]app.AppInfoV1, 0, len(apps)) + for _, a := range apps { + version, err := strconv.ParseUint(a.Version, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse app version: %w", err) + } + + createdAtSec, err := strconv.ParseInt(a.CreatedAt, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse created_at: %w", err) + } + + updatedAtSec, err := strconv.ParseInt(a.UpdatedAt, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse updated_at: %w", err) + } + + result = append(result, app.AppInfoV1{ + App: app.AppV1{ + ID: a.ID, + OwnerWallet: a.OwnerWallet, + Metadata: a.Metadata, + Version: version, + CreationApprovalNotRequired: a.CreationApprovalNotRequired, + }, + CreatedAt: time.Unix(createdAtSec, 0), + UpdatedAt: time.Unix(updatedAtSec, 0), + }) + } + return result, nil +} + +// transformAppToRPC converts app.AppV1 to rpc.AppV1. +func transformAppToRPC(a app.AppV1) rpc.AppV1 { + return rpc.AppV1{ + ID: a.ID, + OwnerWallet: a.OwnerWallet, + Metadata: a.Metadata, + Version: strconv.FormatUint(a.Version, 10), + CreationApprovalNotRequired: a.CreationApprovalNotRequired, + } +} diff --git a/sdk/go/app_session.go b/sdk/go/app_session.go index a9dd23937..e3627cb49 100644 --- a/sdk/go/app_session.go +++ b/sdk/go/app_session.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" ) @@ -99,12 +99,20 @@ func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*ap return &def, nil } +// CreateAppSessionOptions contains optional parameters for CreateAppSession. +type CreateAppSessionOptions struct { + // OwnerSig is the app owner's signature approving session creation. + // Required when the app's CreationApprovalNotRequired is false. + OwnerSig string +} + // CreateAppSession creates a new application session between participants. // // Parameters: // - definition: The app definition with participants, quorum, application ID // - sessionData: Optional JSON stringified session data // - quorumSigs: Participant signatures for the app session creation +// - opts: Optional parameters (owner signature for apps requiring approval) // // Returns: // - AppSessionID of the created session @@ -121,12 +129,15 @@ func (c *Client) GetAppDefinition(ctx context.Context, appSessionID string) (*ap // Nonce: 1, // } // sessionID, version, status, err := client.CreateAppSession(ctx, def, "{}", []string{"sig1", "sig2"}) -func (c *Client) CreateAppSession(ctx context.Context, definition app.AppDefinitionV1, sessionData string, quorumSigs []string) (string, string, string, error) { +func (c *Client) CreateAppSession(ctx context.Context, definition app.AppDefinitionV1, sessionData string, quorumSigs []string, opts ...CreateAppSessionOptions) (string, string, string, error) { req := rpc.AppSessionsV1CreateAppSessionRequest{ Definition: transformAppDefinitionToRPC(definition), SessionData: sessionData, QuorumSigs: quorumSigs, } + if len(opts) > 0 && opts[0].OwnerSig != "" { + req.OwnerSig = opts[0].OwnerSig + } resp, err := c.rpcClient.AppSessionsV1CreateAppSession(ctx, req) if err != nil { return "", "", "", fmt.Errorf("failed to create app session: %w", err) diff --git a/sdk/go/asset_cache.go b/sdk/go/asset_cache.go index c077ced83..4b710dd81 100644 --- a/sdk/go/asset_cache.go +++ b/sdk/go/asset_cache.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/erc7824/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/core" ) // clientAssetStore implements core.AssetStore by fetching data from the Clearnode API. diff --git a/sdk/go/asset_cache_test.go b/sdk/go/asset_cache_test.go index 8547f166c..465f97410 100644 --- a/sdk/go/asset_cache_test.go +++ b/sdk/go/asset_cache_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/sdk/go/channel.go b/sdk/go/channel.go index e7fb13987..f190a3e2e 100644 --- a/sdk/go/channel.go +++ b/sdk/go/channel.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/shopspring/decimal" ) @@ -566,12 +566,11 @@ func (c *Client) Checkpoint(ctx context.Context, asset string) (string, error) { blockchainID := state.HomeLedger.BlockchainID // Initialize blockchain client if needed - if err := c.initializeBlockchainClient(ctx, blockchainID); err != nil { + blockchainClient, err := c.getOrInitBlockchainClient(ctx, blockchainID) + if err != nil { return "", err } - blockchainClient := c.blockchainClients[blockchainID] - // Get home channel info to determine on-chain status channel, err := c.GetHomeChannel(ctx, userWallet, asset) if err != nil { @@ -699,11 +698,11 @@ func (c *Client) Challenge(ctx context.Context, state core.State) (string, error // Initialize blockchain client and submit blockchainID := state.HomeLedger.BlockchainID - if err := c.initializeBlockchainClient(ctx, blockchainID); err != nil { + blockchainClient, err := c.getOrInitBlockchainClient(ctx, blockchainID) + if err != nil { return "", err } - blockchainClient := c.blockchainClients[blockchainID] txHash, err := blockchainClient.Challenge(state, challengerSig, core.ChannelParticipantUser) if err != nil { return "", fmt.Errorf("failed to challenge on blockchain: %w", err) @@ -980,11 +979,11 @@ func (c *Client) SignChannelSessionKeyState(state core.ChannelSessionKeyStateV1) // - Transaction hash of the approval transaction // - Error if the operation fails or the asset is a native token func (c *Client) ApproveToken(ctx context.Context, chainID uint64, asset string, amount decimal.Decimal) (string, error) { - if err := c.initializeBlockchainClient(ctx, chainID); err != nil { + blockchainClient, err := c.getOrInitBlockchainClient(ctx, chainID) + if err != nil { return "", err } - blockchainClient := c.blockchainClients[chainID] return blockchainClient.Approve(asset, amount) } @@ -1003,10 +1002,10 @@ func (c *Client) ApproveToken(ctx context.Context, chainID uint64, asset string, // Requirements: // - Blockchain RPC must be configured for the chain (use WithBlockchainRPC) func (c *Client) GetOnChainBalance(ctx context.Context, chainID uint64, asset string, wallet string) (decimal.Decimal, error) { - if err := c.initializeBlockchainClient(ctx, chainID); err != nil { + blockchainClient, err := c.getOrInitBlockchainClient(ctx, chainID) + if err != nil { return decimal.Zero, err } - blockchainClient := c.blockchainClients[chainID] return blockchainClient.GetTokenBalance(asset, wallet) } diff --git a/sdk/go/client.go b/sdk/go/client.go index 9aaf16e2f..f2ef949aa 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -7,13 +7,14 @@ import ( "sync" "time" - "github.com/erc7824/nitrolite/pkg/blockchain/evm" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/ethclient" + "github.com/layer-3/nitrolite/pkg/blockchain/evm" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" + "github.com/shopspring/decimal" ) // Client provides a unified interface for interacting with Clearnode. @@ -29,7 +30,7 @@ import ( // stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex) // txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex) // client, _ := sdk.NewClient( -// "wss://clearnode.example.com/ws", +// "wss://clearnode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -47,22 +48,24 @@ import ( // config, _ := client.GetConfig(ctx) // balances, _ := client.GetBalances(ctx, walletAddress) type Client struct { - rpcClient *rpc.Client - config Config - exitCh chan struct{} - closeOnce sync.Once - blockchainClients map[uint64]core.Client - homeBlockchains map[string]uint64 - stateSigner core.ChannelSigner - rawSigner sign.Signer - assetStore *clientAssetStore + rpcClient *rpc.Client + config Config + exitCh chan struct{} + closeOnce sync.Once + chainsMu sync.Mutex + blockchainClients map[uint64]core.BlockchainClient + blockchainLockingClients map[uint64]*evm.LockingClient + homeBlockchains map[string]uint64 + stateSigner core.ChannelSigner + rawSigner sign.Signer + assetStore *clientAssetStore } // NewClient creates a new Clearnode client with both high-level and low-level methods. // This is the recommended constructor for most use cases. // // Parameters: -// - wsURL: WebSocket URL of the Clearnode server (e.g., "wss://clearnode.example.com/ws") +// - wsURL: WebSocket URL of the Clearnode server (e.g., "wss://clearnode-sandbox.yellow.org/v1/ws") // - stateSigner: core.ChannelSigner for signing channel states (use sign.NewEthereumMsgSigner) // - txSigner: sign.Signer for signing blockchain transactions (use sign.NewEthereumRawSigner) // - opts: Optional configuration (WithBlockchainRPC, WithHandshakeTimeout, etc.) @@ -76,7 +79,7 @@ type Client struct { // stateSigner, _ := sign.NewEthereumMsgSigner(privateKeyHex) // txSigner, _ := sign.NewEthereumRawSigner(privateKeyHex) // client, err := sdk.NewClient( -// "wss://clearnode.example.com/ws", +// "wss://clearnode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://polygon-amoy.alchemy.com/v2/KEY"), @@ -101,13 +104,14 @@ func NewClient(wsURL string, stateSigner core.ChannelSigner, rawSigner sign.Sign // Create client instance client := &Client{ - rpcClient: rpcClient, - config: config, - exitCh: make(chan struct{}), - blockchainClients: make(map[uint64]core.Client), - homeBlockchains: make(map[string]uint64), - stateSigner: stateSigner, - rawSigner: rawSigner, + rpcClient: rpcClient, + config: config, + exitCh: make(chan struct{}), + blockchainClients: make(map[uint64]core.BlockchainClient), + blockchainLockingClients: make(map[uint64]*evm.LockingClient), + homeBlockchains: make(map[string]uint64), + stateSigner: stateSigner, + rawSigner: rawSigner, } // Create asset store @@ -296,41 +300,43 @@ func WithBlockchainRPC(chainID uint64, rpcURL string) Option { } } -// initializeBlockchainClient initializes a blockchain client for a specific chain. -// This is called lazily when a blockchain operation is needed. -func (c *Client) initializeBlockchainClient(ctx context.Context, chainID uint64) error { +// getOrInitBlockchainClient returns the blockchain client for a specific chain. +func (c *Client) getOrInitBlockchainClient(ctx context.Context, chainID uint64) (core.BlockchainClient, error) { + c.chainsMu.Lock() + defer c.chainsMu.Unlock() + // Check if already initialized - if _, exists := c.blockchainClients[chainID]; exists { - return nil + if bc, exists := c.blockchainClients[chainID]; exists { + return bc, nil } // Get RPC URL from config rpcURL, exists := c.config.BlockchainRPCs[chainID] if !exists { - return fmt.Errorf("blockchain RPC not configured for chain %d (use WithBlockchainRPC)", chainID) + return nil, fmt.Errorf("blockchain RPC not configured for chain %d (use WithBlockchainRPC)", chainID) } - // Get contract address for this blockchain - contractAddress, err := c.getContractAddress(ctx, chainID) + // Get channel hub address for this blockchain + channelHubAddress, err := c.getChannelHubAddress(ctx, chainID) if err != nil { - return err + return nil, err } // Get node address nodeAddress, err := c.getNodeAddress(ctx) if err != nil { - return err + return nil, err } // Connect to blockchain ethClient, err := ethclient.Dial(rpcURL) if err != nil { - return fmt.Errorf("failed to connect to blockchain RPC: %w", err) + return nil, fmt.Errorf("failed to connect to blockchain RPC: %w", err) } // Create blockchain client using the user's signer and node address - evmClient, err := evm.NewClient( - common.HexToAddress(contractAddress), + evmClient, err := evm.NewBlockchainClient( + common.HexToAddress(channelHubAddress), ethClient, c.rawSigner, chainID, @@ -339,11 +345,11 @@ func (c *Client) initializeBlockchainClient(ctx context.Context, chainID uint64) ) if err != nil { - return fmt.Errorf("failed to create blockchain client: %w", err) + return nil, fmt.Errorf("failed to create blockchain client: %w", err) } c.blockchainClients[chainID] = evmClient - return nil + return evmClient, nil } // generateNonce generates a random 8-byte nonce for channel creation. @@ -373,8 +379,8 @@ func (c *Client) getTokenAddress(ctx context.Context, blockchainID uint64, asset return "", fmt.Errorf("asset %s not found", asset) } -// getContractAddress retrieves the channel hub contract address for a specific blockchain from node config. -func (c *Client) getContractAddress(ctx context.Context, blockchainID uint64) (string, error) { +// getChannelHubAddress retrieves the channel hub contract address for a specific blockchain from node config. +func (c *Client) getChannelHubAddress(ctx context.Context, blockchainID uint64) (string, error) { nodeConfig, err := c.GetConfig(ctx) if err != nil { return "", fmt.Errorf("failed to get node config: %w", err) @@ -382,6 +388,9 @@ func (c *Client) getContractAddress(ctx context.Context, blockchainID uint64) (s for _, bc := range nodeConfig.Blockchains { if bc.ID == blockchainID { + if bc.ChannelHubAddress == "" { + return "", fmt.Errorf("channel hub address not configured for blockchain %d", blockchainID) + } return bc.ChannelHubAddress, nil } } @@ -389,6 +398,62 @@ func (c *Client) getContractAddress(ctx context.Context, blockchainID uint64) (s return "", fmt.Errorf("blockchain %d not found in node config", blockchainID) } +// getLockingContractAddress retrieves the Locking contract address for a specific blockchain from node config. +func (c *Client) getLockingContractAddress(ctx context.Context, blockchainID uint64) (string, error) { + nodeConfig, err := c.GetConfig(ctx) + if err != nil { + return "", fmt.Errorf("failed to get node config: %w", err) + } + + for _, bc := range nodeConfig.Blockchains { + if bc.ID == blockchainID { + if bc.LockingContractAddress == "" { + return "", fmt.Errorf("locking contract address not configured for blockchain %d", blockchainID) + } + return bc.LockingContractAddress, nil + } + } + + return "", fmt.Errorf("blockchain %d not found in node config", blockchainID) +} + +// getOrInitLockingClient returns the locking client for a specific chain, +// initializing it lazily if needed. Thread-safe. +func (c *Client) getOrInitLockingClient(ctx context.Context, chainID uint64) (*evm.LockingClient, error) { + c.chainsMu.Lock() + defer c.chainsMu.Unlock() + + if lc, exists := c.blockchainLockingClients[chainID]; exists { + return lc, nil + } + + rpcURL, exists := c.config.BlockchainRPCs[chainID] + if !exists { + return nil, fmt.Errorf("blockchain RPC not configured for chain %d (use WithBlockchainRPC)", chainID) + } + + lockingContractAddress, err := c.getLockingContractAddress(ctx, chainID) + if err != nil { + return nil, err + } + + ethClient, err := ethclient.Dial(rpcURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to blockchain RPC: %w", err) + } + client, err := evm.NewLockingClient( + common.HexToAddress(lockingContractAddress), + ethClient, + chainID, + c.rawSigner, + ) + if err != nil { + return nil, fmt.Errorf("failed to create Locking client: %w", err) + } + c.blockchainLockingClients[chainID] = client + return client, nil +} + // getNodeAddress retrieves the node's Ethereum address from the node config. func (c *Client) getNodeAddress(ctx context.Context) (string, error) { nodeConfig, err := c.GetConfig(ctx) @@ -407,3 +472,125 @@ func (c *Client) getSupportedSigValidatorsBitmap(ctx context.Context) (string, e } return core.BuildSigValidatorsBitmap(nodeConfig.SupportedSigValidators), nil } + +// ============================================================================ +// Locking On-Chain Methods +// ============================================================================ + +// EscrowSecurityTokens locks tokens into the Locking contract on the specified blockchain. +// The tokens are locked for the caller's own address. Before calling this method, +// you must approve the Locking to spend your tokens using ApproveSecurityToken. +// +// Parameters: +// - ctx: Context for the operation +// - destinationWalletAddress: The Ethereum address to lock tokens for +// - blockchainID: The blockchain network ID +// - amount: The amount of tokens to lock (in human-readable decimals, e.g., 100.5 USDC) +// +// Returns: +// - Transaction hash +// - Error if the operation fails +func (c *Client) EscrowSecurityTokens(ctx context.Context, targetWalletAddress string, blockchainID uint64, amount decimal.Decimal) (string, error) { + lc, err := c.getOrInitLockingClient(ctx, blockchainID) + if err != nil { + return "", err + } + return lc.Lock(targetWalletAddress, amount) +} + +// InitiateSecurityTokensWithdrawal initiates the unlock process for locked tokens in the Locking contract. +// After the unlock period elapses, Withdraw Security Tokens can be called to retrieve the tokens. +// +// Parameters: +// - ctx: Context for the operation +// - blockchainID: The blockchain network ID +// +// Returns: +// - Transaction hash +// - Error if the operation fails +func (c *Client) InitiateSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error) { + lc, err := c.getOrInitLockingClient(ctx, blockchainID) + if err != nil { + return "", err + } + + return lc.Unlock() +} + +// CancelSecurityTokensWithdrawal re-locks tokens that are currently in the unlocking state, +// cancelling the pending unlock and returning them to the locked state. +// +// Parameters: +// - ctx: Context for the operation +// - blockchainID: The blockchain network ID +// +// Returns: +// - Transaction hash +// - Error if the operation fails +func (c *Client) CancelSecurityTokensWithdrawal(ctx context.Context, blockchainID uint64) (string, error) { + lc, err := c.getOrInitLockingClient(ctx, blockchainID) + if err != nil { + return "", err + } + + return lc.Relock() +} + +// WithdrawSecurityTokens withdraws unlocked tokens from the Locking contract to the specified destination. +// Can only be called after the unlock period has fully elapsed. +// +// Parameters: +// - ctx: Context for the operation +// - blockchainID: The blockchain network ID +// - destinationWalletAddress: The Ethereum address to receive the withdrawn tokens +// +// Returns: +// - Transaction hash +// - Error if the operation fails +func (c *Client) WithdrawSecurityTokens(ctx context.Context, blockchainID uint64, destinationWalletAddress string) (string, error) { + lc, err := c.getOrInitLockingClient(ctx, blockchainID) + if err != nil { + return "", err + } + + return lc.Withdraw(destinationWalletAddress) +} + +// ApproveSecurityToken approves the Locking contract to spend tokens on behalf of the caller. +// This must be called before Lock Security Tokens. +// +// Parameters: +// - ctx: Context for the operation +// - chainID: The blockchain network ID +// - amount: The amount of tokens to approve +// +// Returns: +// - Transaction hash +// - Error if the operation fails +func (c *Client) ApproveSecurityToken(ctx context.Context, chainID uint64, amount decimal.Decimal) (string, error) { + lc, err := c.getOrInitLockingClient(ctx, chainID) + if err != nil { + return "", err + } + + return lc.ApproveToken(amount) +} + +// GetLockedBalance returns the locked balance of a user in the Locking contract. +// +// Parameters: +// - ctx: Context for the operation +// - chainID: The blockchain network ID +// - wallet: The Ethereum address to check +// +// Returns: +// - The locked balance as a decimal (adjusted for token decimals) +// - Error if the query fails +func (c *Client) GetLockedBalance(ctx context.Context, chainID uint64, wallet string) (decimal.Decimal, error) { + lc, err := c.getOrInitLockingClient(ctx, chainID) + if err != nil { + return decimal.Zero, err + } + + return lc.GetBalance(wallet) +} diff --git a/sdk/go/client_test.go b/sdk/go/client_test.go index 4dbbc7db4..39e135f9b 100644 --- a/sdk/go/client_test.go +++ b/sdk/go/client_test.go @@ -6,12 +6,12 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" - "github.com/erc7824/nitrolite/pkg/sign" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/sign" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -168,11 +168,13 @@ func TestClient_GetAppSessions(t *testing.T) { AppSessions: []rpc.AppSessionInfoV1{ { AppSessionID: "0xSessionID", - Participants: []rpc.AppParticipantV1{}, - Allocations: []rpc.AppAllocationV1{}, - Status: "open", - Nonce: "1", - Version: "1", + AppDefinitionV1: rpc.AppDefinitionV1{ + Participants: []rpc.AppParticipantV1{}, + Nonce: "1", + }, + Allocations: []rpc.AppAllocationV1{}, + Status: "open", + Version: "1", }, }, Metadata: rpc.PaginationMetadataV1{TotalCount: 1}, @@ -211,7 +213,7 @@ func TestClient_GetAppDefinition(t *testing.T) { def, err := client.GetAppDefinition(context.Background(), "0xSessionID") require.NoError(t, err) - assert.Equal(t, "0xApp", def.Application) + assert.Equal(t, "0xApp", def.ApplicationID) assert.Equal(t, uint64(1), def.Nonce) } @@ -232,7 +234,7 @@ func TestClient_CreateAppSession(t *testing.T) { } def := app.AppDefinitionV1{ - Application: "chess-v1", + ApplicationID: "chess-v1", Participants: []app.AppParticipantV1{ {WalletAddress: "0xAlice", SignatureWeight: 1}, {WalletAddress: "0xBob", SignatureWeight: 1}, diff --git a/sdk/go/doc.go b/sdk/go/doc.go index 517e6e433..c15f85390 100644 --- a/sdk/go/doc.go +++ b/sdk/go/doc.go @@ -29,8 +29,8 @@ // "log" // "time" // -// "github.com/erc7824/nitrolite/pkg/sign" -// sdk "github.com/erc7824/nitrolite/sdk/go" +// "github.com/layer-3/nitrolite/pkg/sign" +// sdk "github.com/layer-3/nitrolite/sdk/go" // "github.com/shopspring/decimal" // ) // @@ -48,7 +48,7 @@ // // // Create Client // client, err := sdk.NewClient( -// "wss://clearnode.example.com/ws", +// "wss://clearnode-sandbox.yellow.org/v1/ws", // stateSigner, // txSigner, // sdk.WithBlockchainRPC(80002, "https://rpc-endpoint.example.com"), diff --git a/sdk/go/examples/app_sessions/lifecycle.go b/sdk/go/examples/app_sessions/lifecycle.go index a2ffe1d86..bc2da6fb4 100644 --- a/sdk/go/examples/app_sessions/lifecycle.go +++ b/sdk/go/examples/app_sessions/lifecycle.go @@ -2,56 +2,77 @@ package main // Example: Complete App Session Lifecycle // +// Prerequisites (minimum channel balances): +// - Wallet 1: 0.0001 USDC +// - Wallet 2: 0.00015 WETH +// - Wallet 3: no balance required (receives funds via redistribution) +// // This example demonstrates: -// 1. Create first app session for wallet 1 -// 2. Deposit USDC into first app session by wallet 1 -// 3. Create second app session for wallet 2 with wallet 3 as a participant -// 4. Deposit WETH into second app session by wallet 2 -// 5. Redistribute app state within app session so that participant with wallet 3 also has some allocation -// 6. Rebalance 2 app sessions atomically +// 1. Register apps in the app registry (required before creating app sessions) +// 2. Create first app session for wallet 1 +// 3. Deposit USDC into first app session by wallet 1 +// 4. Create second app session for wallet 2 with wallet 3 as a participant +// 5. Deposit WETH into second app session by wallet 2 +// 6. Redistribute app state within app session so that participant with wallet 3 also has some allocation // 7. Wallet 3 withdraws from his app session // 8. Close both app sessions +// 9. Fail case: attempt to create app session for unregistered app (expected to fail) import ( "context" "fmt" "log" + "math/rand" "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/shopspring/decimal" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" ) func main() { ctx := context.Background() - wsURL := "wss://deployment.yellow.org/ws" + wsURL := "wss://clearnode-sandbox.yellow.org/v1/ws" // --- 0. Setup Wallets --- // Replace these strings with your actual hex private keys - wallet1PrivateKey := "0x7d60..." - wallet2PrivateKey := "0x9b65..." - wallet3PrivateKey := "0xf636..." + wallet1PrivateKey := "0x7d607..." + wallet2PrivateKey := "0x9b652..." + wallet3PrivateKey := "0xf6369..." - // Create signers from private keys - wallet1Signer, err := sign.NewEthereumMsgSigner(wallet1PrivateKey) + // Create raw signers from private keys + wallet1RawSigner, err := sign.NewEthereumRawSigner(wallet1PrivateKey) if err != nil { log.Fatalf("Invalid wallet 1 private key: %v", err) } - wallet2Signer, err := sign.NewEthereumMsgSigner(wallet2PrivateKey) + wallet2RawSigner, err := sign.NewEthereumRawSigner(wallet2PrivateKey) if err != nil { log.Fatalf("Invalid wallet 2 private key: %v", err) } - wallet3Signer, err := sign.NewEthereumMsgSigner(wallet3PrivateKey) + wallet3RawSigner, err := sign.NewEthereumRawSigner(wallet3PrivateKey) if err != nil { log.Fatalf("Invalid wallet 3 private key: %v", err) } + // Create msg signers (EIP-191 prefixed) for channel and app session signing + wallet1Signer, err := sign.NewEthereumMsgSignerFromRaw(wallet1RawSigner) + if err != nil { + log.Fatalf("Failed to create msg signer for wallet 1: %v", err) + } + wallet2Signer, err := sign.NewEthereumMsgSignerFromRaw(wallet2RawSigner) + if err != nil { + log.Fatalf("Failed to create msg signer for wallet 2: %v", err) + } + wallet3Signer, err := sign.NewEthereumMsgSignerFromRaw(wallet3RawSigner) + if err != nil { + log.Fatalf("Failed to create msg signer for wallet 3: %v", err) + } + channel1Signer, err := core.NewChannelDefaultSigner(wallet1Signer) if err != nil { log.Fatalf("Failed to create channel signer for wallet 1: %v", err) @@ -62,7 +83,7 @@ func main() { } channel3Signer, err := core.NewChannelDefaultSigner(wallet3Signer) if err != nil { - log.Fatalf("Failed to create channel signer for wallet 2: %v", err) + log.Fatalf("Failed to create channel signer for wallet 3: %v", err) } appSession1Signer, err := app.NewAppSessionWalletSignerV1(wallet1Signer) @@ -75,9 +96,9 @@ func main() { } // Extract wallet addresses - wallet1Address := wallet1Signer.PublicKey().Address().String() // 0x053aEAD7d3eebE4359300fDE849bCD9E77384989 - wallet2Address := wallet2Signer.PublicKey().Address().String() // 0x2BfA10aAd64Ae0F7855f54f27117Fcc9C61C6770 - wallet3Address := wallet3Signer.PublicKey().Address().String() // 0xaB5670b44cb4A3B5535BD637cb600DA572148c98 + wallet1Address := wallet1RawSigner.PublicKey().Address().String() + wallet2Address := wallet2RawSigner.PublicKey().Address().String() + wallet3Address := wallet3RawSigner.PublicKey().Address().String() fmt.Println("--- Wallets Imported ---") fmt.Printf("Wallet 1 Address: %s\n", wallet1Address) @@ -86,16 +107,41 @@ func main() { fmt.Println("------------------------") // Create SDK clients (in a real app, these would be separate instances) - wallet1Client, err := sdk.NewClient(wsURL, channel1Signer, wallet1Signer) + wallet1Client, err := sdk.NewClient(wsURL, channel1Signer, wallet1RawSigner) + if err != nil { + log.Fatal(err) + } + wallet2Client, err := sdk.NewClient(wsURL, channel2Signer, wallet2RawSigner) if err != nil { log.Fatal(err) } + wallet3Client, err := sdk.NewClient(wsURL, channel3Signer, wallet3RawSigner) + if err != nil { + log.Fatal(err) + } + + // --- 1. Register Apps --- + fmt.Println("=== Step 1: Registering Apps ===") + + suffix := fmt.Sprintf("%06d", rand.Intn(1000000)) + app1ID := "test-app-" + suffix + app2ID := "multi-party-app-" + suffix + + if err := wallet1Client.RegisterApp(ctx, app1ID, "{}", true); err != nil { + log.Fatalf("Failed to register %s: %v", app1ID, err) + } + fmt.Printf("✓ Registered app: %s\n", app1ID) + + if err := wallet1Client.RegisterApp(ctx, app2ID, "{}", false); err != nil { + log.Fatalf("Failed to register %s: %v", app2ID, err) + } + fmt.Printf("✓ Registered app: %s (owner approval required)\n\n", app2ID) - // --- 1. Create App Session 1 (Single Participant: Wallet 1) --- - fmt.Println("=== Step 1: Creating App Session 1 (Wallet 1 only) ===") + // --- 2. Create App Session 1 (Single Participant: Wallet 1) --- + fmt.Println("=== Step 2: Creating App Session 1 (Wallet 1 only) ===") session1Definition := app.AppDefinitionV1{ - Application: "test-app", + ApplicationID: app1ID, Participants: []app.AppParticipantV1{ {WalletAddress: wallet1Address, SignatureWeight: 100}, }, @@ -115,8 +161,8 @@ func main() { } fmt.Printf("✓ Created App Session 1: %s\n\n", session1ID) - // --- 2. Deposit USDC into Session 1 --- - fmt.Println("=== Step 2: Depositing USDC into Session 1 ===") + // --- 3. Deposit USDC into Session 1 --- + fmt.Println("=== Step 3: Depositing USDC into Session 1 ===") session1DepositAmount := decimal.NewFromFloat(0.0001) session1DepositUpdate := app.AppStateUpdateV1{ @@ -139,19 +185,10 @@ func main() { } fmt.Printf("✓ Deposited %s USDC into Session 1\n\n", session1DepositAmount) - // --- 3. Create App Session 2 (Multi-Party: Wallet 2 & 3) --- - fmt.Println("=== Step 3: Creating App Session 2 (Wallet 2 & 3) ===") + // --- 4. Create App Session 2 (Multi-Party: Wallet 2 & 3) --- + fmt.Println("=== Step 4: Creating App Session 2 (Wallet 2 & 3) ===") - appID := "multi-party-app" - wallet2Client, err := sdk.NewClient(wsURL, channel2Signer, wallet2Signer) - if err != nil { - log.Fatal(err) - } - - wallet3Client, err := sdk.NewClient(wsURL, channel3Signer, wallet3Signer) // No channel signer needed for wallet 3 since it's not creating channels - if err != nil { - log.Fatal(err) - } + appID := app2ID msgSigner3, err := generateMsgSigner() if err != nil { @@ -186,7 +223,7 @@ func main() { } session2Definition := app.AppDefinitionV1{ - Application: appID, + ApplicationID: appID, Participants: []app.AppParticipantV1{ {WalletAddress: wallet2Address, SignatureWeight: 50}, {WalletAddress: wallet3Address, SignatureWeight: 50}, @@ -202,16 +239,23 @@ func main() { appSession2CreateSession2Sig, _ := appSession2Signer.Sign(session2CreateRequest) appSession3CreateSession2Sig, _ := appSession3Signer.Sign(session2CreateRequest) - session2ID, _, _, err := wallet2Client.CreateAppSession(ctx, session2Definition, "{}", []string{appSession2CreateSession2Sig.String(), appSession3CreateSession2Sig.String()}) + + // Owner approval: wallet1 is the owner of app2, sign the create request using app session signer + ownerApprovalSig, err := appSession1Signer.Sign(session2CreateRequest) + if err != nil { + log.Fatalf("Failed to sign owner approval: %v", err) + } + + session2ID, _, _, err := wallet2Client.CreateAppSession(ctx, session2Definition, "{}", []string{appSession2CreateSession2Sig.String(), appSession3CreateSession2Sig.String()}, sdk.CreateAppSessionOptions{OwnerSig: ownerApprovalSig.String()}) if err != nil { log.Fatal(err) } fmt.Printf("✓ Created App Session 2: %s\n\n", session2ID) - // --- 4. Deposit WETH into Session 2 by Wallet 2 --- - fmt.Println("=== Step 4: Depositing WETH into Session 2 ===") + // --- 5. Deposit WETH into Session 2 by Wallet 2 --- + fmt.Println("=== Step 5: Depositing WETH into Session 2 ===") - session2DepositAmount := decimal.NewFromFloat(0.015) + session2DepositAmount := decimal.NewFromFloat(0.00015) session2DepositUpdate := app.AppStateUpdateV1{ AppSessionID: session2ID, Intent: app.AppStateUpdateIntentDeposit, @@ -242,16 +286,16 @@ func main() { fmt.Printf("Session 2 before redistribution - Version: %d, Allocations: %+v\n\n", session2InfoBeforeRedist[0].Version, session2InfoBeforeRedist[0].Allocations) } - // --- 5. Redistribute within Session 2 (Wallet 2 -> Wallet 3) --- - fmt.Println("=== Step 5: Redistributing funds in Session 2 ===") + // --- 6. Redistribute within Session 2 (Wallet 2 -> Wallet 3) --- + fmt.Println("=== Step 6: Redistributing funds in Session 2 ===") session2RedistributeUpdate := app.AppStateUpdateV1{ AppSessionID: session2ID, Intent: app.AppStateUpdateIntentOperate, Version: 3, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.01)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, + {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.0001)}, + {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, }, } @@ -268,83 +312,84 @@ func main() { if err != nil { log.Fatalf("Redistribution failed: %v", err) } - fmt.Println("✓ Redistributed WETH: Wallet 2 (0.01) -> Wallet 3 (0.005)") - - // --- 6. Rebalance Both App Sessions Atomically --- - fmt.Println("=== Step 6: Atomic Rebalance Across Sessions ===") - - // Check current allocations before rebalance - session1InfoBeforeRebalance, _, err := wallet1Client.GetAppSessions(ctx, &sdk.GetAppSessionsOptions{AppSessionID: &session1ID}) - if err != nil { - log.Fatal(err) - } - if len(session1InfoBeforeRebalance) > 0 { - fmt.Printf("Session 1 before rebalance - Version: %d, Allocations: %+v\n", session1InfoBeforeRebalance[0].Version, session1InfoBeforeRebalance[0].Allocations) - } - - session2InfoBeforeRebalance, _, err := wallet2Client.GetAppSessions(ctx, &sdk.GetAppSessionsOptions{AppSessionID: &session2ID}) - if err != nil { - log.Fatal(err) - } - if len(session2InfoBeforeRebalance) > 0 { - fmt.Printf("Session 2 before rebalance - Version: %d, Allocations: %+v\n\n", session2InfoBeforeRebalance[0].Version, session2InfoBeforeRebalance[0].Allocations) - } - - // Prepare rebalance updates for both sessions - session1RebalanceUpdate := app.AppStateUpdateV1{ - AppSessionID: session1ID, - Intent: app.AppStateUpdateIntentRebalance, - Version: 3, - Allocations: []app.AppAllocationV1{ - {Participant: wallet1Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - {Participant: wallet1Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, - }, - } - - session1RebalanceRequest, err := app.PackAppStateUpdateV1(session1RebalanceUpdate) - if err != nil { - log.Fatal(err) - } - - appSession1RebalanceSig, _ := appSession1Signer.Sign(session1RebalanceRequest) - - session2RebalanceUpdate := app.AppStateUpdateV1{ - AppSessionID: session2ID, - Intent: app.AppStateUpdateIntentRebalance, - Version: 4, - Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - }, - } - - session2RebalanceRequest, err := app.PackAppStateUpdateV1(session2RebalanceUpdate) - if err != nil { - log.Fatal(err) - } - - appSession2RebalanceSig, _ := appSession2Signer.Sign(session2RebalanceRequest) - appSession3RebalanceSig, _ := appSession3Signer.Sign(session2RebalanceRequest) - - // Submit atomic rebalance - signedRebalanceUpdates := []app.SignedAppStateUpdateV1{ - { - AppStateUpdate: session1RebalanceUpdate, - QuorumSigs: []string{appSession1RebalanceSig.String()}, - }, - { - AppStateUpdate: session2RebalanceUpdate, - QuorumSigs: []string{appSession2RebalanceSig.String(), appSession3RebalanceSig.String()}, - }, - } - - rebalanceBatchID, err := wallet2Client.RebalanceAppSessions(ctx, signedRebalanceUpdates) - if err != nil { - log.Printf("⚠ Rebalance Error: %v", err) - } else { - fmt.Printf("✓ Atomic Rebalance Submitted. BatchID: %s\n\n", rebalanceBatchID) - } + fmt.Println("✓ Redistributed WETH: Wallet 2 (0.0001) -> Wallet 3 (0.00005)") + + // NOTE: Rebalance step is disabled. + // // --- 7. Rebalance Both App Sessions Atomically --- + // fmt.Println("=== Step 6: Atomic Rebalance Across Sessions ===") + + // // Check current allocations before rebalance + // session1InfoBeforeRebalance, _, err := wallet1Client.GetAppSessions(ctx, &sdk.GetAppSessionsOptions{AppSessionID: &session1ID}) + // if err != nil { + // log.Fatal(err) + // } + // if len(session1InfoBeforeRebalance) > 0 { + // fmt.Printf("Session 1 before rebalance - Version: %d, Allocations: %+v\n", session1InfoBeforeRebalance[0].Version, session1InfoBeforeRebalance[0].Allocations) + // } + + // session2InfoBeforeRebalance, _, err := wallet2Client.GetAppSessions(ctx, &sdk.GetAppSessionsOptions{AppSessionID: &session2ID}) + // if err != nil { + // log.Fatal(err) + // } + // if len(session2InfoBeforeRebalance) > 0 { + // fmt.Printf("Session 2 before rebalance - Version: %d, Allocations: %+v\n\n", session2InfoBeforeRebalance[0].Version, session2InfoBeforeRebalance[0].Allocations) + // } + + // // Prepare rebalance updates for both sessions + // session1RebalanceUpdate := app.AppStateUpdateV1{ + // AppSessionID: session1ID, + // Intent: app.AppStateUpdateIntentRebalance, + // Version: 3, + // Allocations: []app.AppAllocationV1{ + // {Participant: wallet1Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, + // {Participant: wallet1Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, + // }, + // } + + // session1RebalanceRequest, err := app.PackAppStateUpdateV1(session1RebalanceUpdate) + // if err != nil { + // log.Fatal(err) + // } + + // appSession1RebalanceSig, _ := appSession1Signer.Sign(session1RebalanceRequest) + + // session2RebalanceUpdate := app.AppStateUpdateV1{ + // AppSessionID: session2ID, + // Intent: app.AppStateUpdateIntentRebalance, + // Version: 4, + // Allocations: []app.AppAllocationV1{ + // {Participant: wallet2Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, + // {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, + // {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, + // }, + // } + + // session2RebalanceRequest, err := app.PackAppStateUpdateV1(session2RebalanceUpdate) + // if err != nil { + // log.Fatal(err) + // } + + // appSession2RebalanceSig, _ := appSession2Signer.Sign(session2RebalanceRequest) + // appSession3RebalanceSig, _ := appSession3Signer.Sign(session2RebalanceRequest) + + // // Submit atomic rebalance + // signedRebalanceUpdates := []app.SignedAppStateUpdateV1{ + // { + // AppStateUpdate: session1RebalanceUpdate, + // QuorumSigs: []string{appSession1RebalanceSig.String()}, + // }, + // { + // AppStateUpdate: session2RebalanceUpdate, + // QuorumSigs: []string{appSession2RebalanceSig.String(), appSession3RebalanceSig.String()}, + // }, + // } + + // rebalanceBatchID, err := wallet2Client.RebalanceAppSessions(ctx, signedRebalanceUpdates) + // if err != nil { + // log.Printf("⚠ Rebalance Error: %v", err) + // } else { + // fmt.Printf("✓ Atomic Rebalance Submitted. BatchID: %s\n\n", rebalanceBatchID) + // } // --- 7. Wallet 3 Withdraws from Session 2 --- fmt.Println("=== Step 7: Wallet 3 Withdrawing from Session 2 ===") @@ -352,11 +397,10 @@ func main() { session2WithdrawUpdate := app.AppStateUpdateV1{ AppSessionID: session2ID, Intent: app.AppStateUpdateIntentWithdraw, - Version: 5, + Version: 4, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.001)}, + {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00001)}, }, } @@ -372,7 +416,7 @@ func main() { if err != nil { log.Printf("⚠ Withdraw Error: %v", err) } else { - fmt.Println("✓ Wallet 3 successfully withdrew 0.004 WETH back to channel") + fmt.Println("✓ Wallet 3 successfully withdrew WETH back to channel") } // --- 8. Close Both App Sessions --- @@ -382,10 +426,9 @@ func main() { session1CloseUpdate := app.AppStateUpdateV1{ AppSessionID: session1ID, Intent: app.AppStateUpdateIntentClose, - Version: 4, + Version: 3, Allocations: []app.AppAllocationV1{ - {Participant: wallet1Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - {Participant: wallet1Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet1Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.0001)}, }, } @@ -407,11 +450,10 @@ func main() { session2CloseUpdate := app.AppStateUpdateV1{ AppSessionID: session2ID, Intent: app.AppStateUpdateIntentClose, - Version: 6, + Version: 5, Allocations: []app.AppAllocationV1{ - {Participant: wallet2Address, Asset: "usdc", Amount: decimal.NewFromFloat(0.00005)}, - {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.005)}, - {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.001)}, + {Participant: wallet2Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00005)}, + {Participant: wallet3Address, Asset: "weth", Amount: decimal.NewFromFloat(0.00001)}, }, } @@ -430,6 +472,31 @@ func main() { fmt.Println("✓ Session 2 successfully closed") } + // --- 9. Fail Case: Create App Session for Unregistered App --- + fmt.Println("\n=== Step 9: Creating App Session for Unregistered App (expected to fail) ===") + + unregisteredDefinition := app.AppDefinitionV1{ + ApplicationID: "unregistered-app-" + suffix, + Participants: []app.AppParticipantV1{ + {WalletAddress: wallet1Address, SignatureWeight: 100}, + }, + Quorum: 100, + Nonce: uint64(time.Now().UnixNano()), + } + + unregisteredCreateRequest, err := app.PackCreateAppSessionRequestV1(unregisteredDefinition, "{}") + if err != nil { + log.Fatal(err) + } + + unregisteredSig, _ := appSession1Signer.Sign(unregisteredCreateRequest) + _, _, _, err = wallet1Client.CreateAppSession(ctx, unregisteredDefinition, "{}", []string{unregisteredSig.String()}) + if err != nil { + fmt.Printf("✓ Expected error: %v\n", err) + } else { + fmt.Println("✗ Unexpected success: app session was created for unregistered app") + } + fmt.Println("\n=== Example Complete ===") } diff --git a/sdk/go/examples/challenge/main.go b/sdk/go/examples/challenge/main.go index 24735d9d2..cdc9b3da2 100644 --- a/sdk/go/examples/challenge/main.go +++ b/sdk/go/examples/challenge/main.go @@ -20,9 +20,9 @@ import ( "github.com/ethereum/go-ethereum/crypto" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/sign" - sdk "github.com/erc7824/nitrolite/sdk/go" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/sign" + sdk "github.com/layer-3/nitrolite/sdk/go" "github.com/shopspring/decimal" ) @@ -30,7 +30,7 @@ func main() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - wsURL := "wss://deployment.yellow.org/ws" + wsURL := "wss://clearnode-sandbox.yellow.org/v1/ws" privateKeyHex := "0x7d6..." chainID := uint64(11155111) rpcUrl := "https://sepolia.drpc.org" diff --git a/sdk/go/mock_dialer_test.go b/sdk/go/mock_dialer_test.go index c00114438..ef45d6933 100644 --- a/sdk/go/mock_dialer_test.go +++ b/sdk/go/mock_dialer_test.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/rpc" ) type MockDialer struct { diff --git a/sdk/go/node.go b/sdk/go/node.go index a7f5dda77..3f199eaf4 100644 --- a/sdk/go/node.go +++ b/sdk/go/node.go @@ -5,8 +5,8 @@ import ( "fmt" "strconv" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // ============================================================================ diff --git a/sdk/go/user.go b/sdk/go/user.go index c356f423a..6d53b7144 100644 --- a/sdk/go/user.go +++ b/sdk/go/user.go @@ -3,9 +3,10 @@ package sdk import ( "context" "fmt" + "strconv" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" ) // ============================================================================ @@ -86,3 +87,46 @@ func (c *Client) GetTransactions(ctx context.Context, wallet string, opts *GetTr } return txs, transformPaginationMetadata(resp.Metadata), nil } + +// GetActionAllowances retrieves the action allowances for a user based on their staking level. +// +// Parameters: +// - wallet: The user's wallet address +// +// Returns: +// - Slice of ActionAllowance containing allowance information per gated action +// - Error if the request fails +func (c *Client) GetActionAllowances(ctx context.Context, wallet string) ([]core.ActionAllowance, error) { + req := rpc.UserV1GetActionAllowancesRequest{Wallet: wallet} + resp, err := c.rpcClient.UserV1GetActionAllowances(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to get action allowances: %w", err) + } + allowances, err := transformActionAllowances(resp.Allowances) + if err != nil { + return nil, fmt.Errorf("failed to transform action allowances: %w", err) + } + return allowances, nil +} + +// transformActionAllowances converts RPC ActionAllowanceV1 slice to core.ActionAllowance slice. +func transformActionAllowances(allowances []rpc.ActionAllowanceV1) ([]core.ActionAllowance, error) { + result := make([]core.ActionAllowance, 0, len(allowances)) + for _, a := range allowances { + allowance, err := strconv.ParseUint(a.Allowance, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid allowance value %q for action %q: %w", a.Allowance, a.GatedAction, err) + } + used, err := strconv.ParseUint(a.Used, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid used value %q for action %q: %w", a.Used, a.GatedAction, err) + } + result = append(result, core.ActionAllowance{ + GatedAction: a.GatedAction, + TimeWindow: a.TimeWindow, + Allowance: allowance, + Used: used, + }) + } + return result, nil +} diff --git a/sdk/go/utils.go b/sdk/go/utils.go index d3fea8922..50040fb5b 100644 --- a/sdk/go/utils.go +++ b/sdk/go/utils.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" ) @@ -26,10 +26,11 @@ func transformNodeConfig(resp rpc.NodeV1GetConfigResponse) (*core.NodeConfig, er } blockchains = append(blockchains, core.Blockchain{ - Name: info.Name, - ID: blockchainID, - ChannelHubAddress: info.ChannelHubAddress, - BlockStep: 0, // Not provided in RPC response + Name: info.Name, + ID: blockchainID, + ChannelHubAddress: info.ChannelHubAddress, + LockingContractAddress: info.LockingContractAddress, + BlockStep: 0, // Not provided in RPC response }) } @@ -385,13 +386,9 @@ func transformChannelDefinitionToRPC(def core.ChannelDefinition) rpc.ChannelDefi func transformAppSessions(sessions []rpc.AppSessionInfoV1) ([]app.AppSessionInfoV1, error) { result := make([]app.AppSessionInfoV1, 0, len(sessions)) for _, s := range sessions { - // Transform participants - participants := make([]app.AppParticipantV1, 0, len(s.Participants)) - for _, p := range s.Participants { - participants = append(participants, app.AppParticipantV1{ - WalletAddress: p.WalletAddress, - SignatureWeight: p.SignatureWeight, - }) + appDef, err := transformAppDefinition(s.AppDefinitionV1) + if err != nil { + return nil, fmt.Errorf("failed to transform app definition: %w", err) } // Transform allocations @@ -418,25 +415,18 @@ func transformAppSessions(sessions []rpc.AppSessionInfoV1) ([]app.AppSessionInfo sessionData = *s.SessionData } - nonce, err := strconv.ParseUint(s.Nonce, 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse nonce: %w", err) - } - version, err := strconv.ParseUint(s.Version, 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse version: %w", err) } result = append(result, app.AppSessionInfoV1{ - AppSessionID: s.AppSessionID, - IsClosed: isClosed, - Participants: participants, - SessionData: sessionData, - Quorum: s.Quorum, - Version: version, - Nonce: nonce, - Allocations: allocations, + AppSessionID: s.AppSessionID, + AppDefinition: appDef, + IsClosed: isClosed, + SessionData: sessionData, + Version: version, + Allocations: allocations, }) } return result, nil @@ -458,10 +448,10 @@ func transformAppDefinition(def rpc.AppDefinitionV1) (app.AppDefinitionV1, error } return app.AppDefinitionV1{ - Application: def.Application, - Participants: participants, - Quorum: def.Quorum, - Nonce: nonce, + ApplicationID: def.Application, + Participants: participants, + Quorum: def.Quorum, + Nonce: nonce, }, nil } @@ -476,7 +466,7 @@ func transformAppDefinitionToRPC(def app.AppDefinitionV1) rpc.AppDefinitionV1 { } return rpc.AppDefinitionV1{ - Application: def.Application, + Application: def.ApplicationID, Participants: participants, Quorum: def.Quorum, Nonce: strconv.FormatUint(def.Nonce, 10), diff --git a/sdk/go/utils_test.go b/sdk/go/utils_test.go index b1a0f0ae1..71d8a0a96 100644 --- a/sdk/go/utils_test.go +++ b/sdk/go/utils_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" - "github.com/erc7824/nitrolite/pkg/app" - "github.com/erc7824/nitrolite/pkg/core" - "github.com/erc7824/nitrolite/pkg/rpc" + "github.com/layer-3/nitrolite/pkg/app" + "github.com/layer-3/nitrolite/pkg/core" + "github.com/layer-3/nitrolite/pkg/rpc" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -119,7 +119,7 @@ func TestTransformPaginationParams(t *testing.T) { limit := uint32(20) offset := uint32(0) sort := "desc" - + params := &core.PaginationParams{ Limit: &limit, Offset: &offset, @@ -226,7 +226,7 @@ func TestTransformState(t *testing.T) { rpcStateBack := transformStateToRPC(state) assert.Equal(t, rpcState.ID, rpcStateBack.ID) assert.Equal(t, rpcState.Epoch, rpcStateBack.Epoch) - + // Test error cases badState := rpcState badState.Epoch = "invalid" @@ -249,15 +249,17 @@ func TestTransformAppSessions(t *testing.T) { rpcSessions := []rpc.AppSessionInfoV1{ { AppSessionID: "0xSessionID", - Participants: []rpc.AppParticipantV1{ - {WalletAddress: "0xA", SignatureWeight: 1}, + AppDefinitionV1: rpc.AppDefinitionV1{ + Participants: []rpc.AppParticipantV1{ + {WalletAddress: "0xA", SignatureWeight: 1}, + }, + Nonce: "1", }, Allocations: []rpc.AppAllocationV1{ {Participant: "0xA", Asset: "USDC", Amount: "10.0"}, }, Status: "open", SessionData: &sessionData, - Nonce: "1", Version: "1", }, } @@ -268,7 +270,7 @@ func TestTransformAppSessions(t *testing.T) { assert.Equal(t, "0xSessionID", sessions[0].AppSessionID) assert.False(t, sessions[0].IsClosed) assert.Equal(t, "data", sessions[0].SessionData) - assert.Equal(t, uint64(1), sessions[0].Nonce) + assert.Equal(t, uint64(1), sessions[0].AppDefinition.Nonce) // Test error cases rpcSessions[0].Allocations[0].Amount = "invalid" @@ -276,7 +278,7 @@ func TestTransformAppSessions(t *testing.T) { assert.Error(t, err) rpcSessions[0].Allocations[0].Amount = "10.0" - rpcSessions[0].Nonce = "invalid" + rpcSessions[0].AppDefinitionV1.Nonce = "invalid" _, err = transformAppSessions(rpcSessions) assert.Error(t, err) } @@ -293,7 +295,7 @@ func TestTransformAppDefinition(t *testing.T) { def, err := transformAppDefinition(rpcDef) require.NoError(t, err) - assert.Equal(t, "0xApp", def.Application) + assert.Equal(t, "0xApp", def.ApplicationID) assert.Equal(t, uint64(1), def.Nonce) // Reverse @@ -351,7 +353,7 @@ func TestTransformChannelSessionKeyState(t *testing.T) { rpcStateBack := transformChannelSessionKeyStateToRPC(state) assert.Equal(t, rpcState.Version, rpcStateBack.Version) // rpcStateBack.UserAddress might be lowercased now - + // Error cases badState := rpcState badState.Version = "invalid" diff --git a/sdk/package-lock.json b/sdk/package-lock.json new file mode 100644 index 000000000..9699e0851 --- /dev/null +++ b/sdk/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "sdk", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/sdk/ts-compat/README.md b/sdk/ts-compat/README.md index 52b990998..d84ce0d1c 100644 --- a/sdk/ts-compat/README.md +++ b/sdk/ts-compat/README.md @@ -1,6 +1,6 @@ # Nitrolite Compat SDK -[![License](https://img.shields.io/npm/l/@yellow-org/sdk-compat.svg)](https://github.com/erc7824/nitrolite/blob/main/LICENSE) +[![License](https://img.shields.io/npm/l/@yellow-org/sdk-compat.svg)](https://github.com/layer-3/nitrolite/blob/main/LICENSE) Compatibility layer that bridges the Nitrolite SDK **v0.5.3 API** to the **v1.0.0 runtime**, letting existing dApps upgrade to the new protocol with minimal code changes. @@ -86,10 +86,16 @@ const assets = await client.getAssetsList(); ### 4. Transfer off-chain +The compat `transfer(destination, allocations)` preserves the v0.5.3-style array-of-allocations signature. Each `TransferAllocation.amount` is a **raw-unit string** (smallest denomination). The compat layer divides by token decimals before delegating to the v1 SDK's `transfer(wallet, asset, Decimal)`: + ```typescript +// 5 USDC = 5_000_000 raw units (6 decimals) await client.transfer(recipientAddress, [ - { asset: 'usdc', amount: '5.0' }, + { asset: 'usdc', amount: '5000000' }, ]); + +// For direct SDK access with human-readable Decimal: +// await client.innerClient.transfer('0xRecip...', 'usdc', new Decimal(5)); ``` ### 5. Close & clean up @@ -111,6 +117,8 @@ await client.close(); | `closeChannel()` | Close all open channels | | `resizeChannel({ allocate_amount, token })` | Resize an existing channel | | `challengeChannel({ state })` | Challenge a channel on-chain | +| `acknowledge(tokenAddress)` | Acknowledge a pending state or create a channel | +| `checkTokenAllowance(chainId, tokenAddress)` | Check ERC-20 allowance for the ChannelHub | ### Queries @@ -123,6 +131,9 @@ await client.close(); | `getAssetsList()` | List supported assets | | `getAccountInfo()` | Aggregate balance + channel count | | `getConfig()` | Node configuration | +| `getBlockchains()` | List supported blockchains | +| `getActionAllowances(wallet?)` | Get gated action allowances for a wallet | +| `getEscrowChannel(escrowChannelId)` | Query an escrow channel by ID | | `getChannelData(channelId)` | Full channel + state for a specific channel | | `getLastAppSessionsListError()` | Last `getAppSessionsList()` error message (if any) | @@ -130,11 +141,18 @@ await client.close(); | Method | Description | |---|---| -| `createAppSession(definition, allocations, quorumSigs?)` | Create an app session (optionally with quorum signatures) | -| `closeAppSession(appSessionId, allocations, quorumSigs?)` | Close an app session (optionally with quorum signatures) | -| `submitAppState(params)` | Submit state update (operate/deposit/withdraw/close) with optional `quorum_sigs` | +| `createAppSession(definition, allocations, quorumSigs, opts?)` | Create an app session with quorum signatures (optional `ownerSig` via opts) | +| `closeAppSession(appSessionId, allocations, quorumSigs)` | Close an app session with quorum signatures | +| `submitAppState(params)` | Submit state update (operate/deposit/withdraw/close) | | `getAppDefinition(appSessionId)` | Get the definition for a session | +### App Registry + +| Method | Description | +|---|---| +| `getApps(options?)` | List registered applications (filter by appId, owner, pagination) | +| `registerApp(appID, metadata, creationApprovalNotRequired)` | Register a new application | + ### App Session Signing Helpers | Helper | Description | @@ -173,12 +191,24 @@ await client.close(); | `parseAmount(tokenAddress, humanAmount)` | Convert human-readable string → raw bigint | | `findOpenChannel(tokenAddress, chainId?)` | Find an open channel for a given token | +### Security Token Locking + +| Method | Description | +|---|---| +| `lockSecurityTokens(targetWallet, chainId, amount)` | Lock tokens into the Locking contract for a target address | +| `initiateSecurityTokensWithdrawal(chainId)` | Start the unlock process for locked tokens | +| `cancelSecurityTokensWithdrawal(chainId)` | Re-lock tokens, cancelling a pending unlock | +| `withdrawSecurityTokens(chainId, destination)` | Withdraw unlocked tokens to a destination address | +| `approveSecurityToken(chainId, amount)` | Approve the Locking contract to spend tokens | +| `getLockedBalance(chainId, wallet?)` | Query locked balance (returns raw bigint) | + ### Lifecycle | Method | Description | |---|---| | `ping()` | Health check | | `close()` | Close the WebSocket connection | +| `waitForClose()` | Returns a promise that resolves when the connection is closed | | `refreshAssets()` | Re-fetch the asset map from the clearnode | ### Accessing the v1.0.0 SDK Directly @@ -253,6 +283,7 @@ The compat layer provides typed error classes for common failure modes: | `UserRejectedError` | `USER_REJECTED` | User cancelled in wallet | | `InsufficientFundsError` | `INSUFFICIENT_FUNDS` | Not enough balance | | `NotInitializedError` | `NOT_INITIALIZED` | Client not connected | +| `OngoingStateTransitionError` | `ONGOING_STATE_TRANSITION` | Previous action still finalizing | ### `getUserFacingMessage(error)` @@ -305,6 +336,45 @@ poller.stop(); poller.setInterval(10000); // change interval ``` +## Security Token Locking + +Lock tokens into the on-chain Locking contract to provide security deposits. The locking token and its decimals are resolved from the contract at runtime — `blockchainRPCs` must be configured for the target chain or these methods will throw. + +```typescript +const chainId = 11155111; // Sepolia +// Yellow token on Sepolia has 18 decimals; 100 YELLOW = 100 * 10^18 +const amount = 100_000_000_000_000_000_000n; + +// Approve the Locking contract to spend tokens +await client.approveSecurityToken(chainId, amount); + +// Lock tokens for a target address +await client.lockSecurityTokens(targetWallet, chainId, amount); + +// Query locked balance (returns raw bigint in token's smallest unit) +const locked = await client.getLockedBalance(chainId); + +// Initiate unlock (starts the unlock period) +await client.initiateSecurityTokensWithdrawal(chainId); + +// Cancel unlock (re-lock tokens) +await client.cancelSecurityTokensWithdrawal(chainId); + +// After unlock period elapses, withdraw to a destination +await client.withdrawSecurityTokens(chainId, destinationWallet); +``` + +### Amount conventions + +The compat layer accepts raw amounts (smallest token unit) and converts to human-readable `Decimal` before delegating to the v1 SDK. + +| Method group | Input type | Example: 100 tokens (18 decimals) | +|---|---|---| +| `deposit`, `withdrawal`, `lockSecurityTokens`, `approveSecurityToken`, `getLockedBalance` | Raw `bigint` | `100_000_000_000_000_000_000n` | +| `transfer` | Raw string via `TransferAllocation.amount` | `'100000000000000000000'` | + +> For direct access to the v1 SDK's human-readable `Decimal` API, use `client.innerClient`. + ## RPC Stubs The following functions exist so that any remaining v0.5.3 `create*Message` / `parse*Response` imports compile. diff --git a/sdk/ts-compat/docs/migration-offchain.md b/sdk/ts-compat/docs/migration-offchain.md index 5744920ae..8b0a77d44 100644 --- a/sdk/ts-compat/docs/migration-offchain.md +++ b/sdk/ts-compat/docs/migration-offchain.md @@ -57,7 +57,7 @@ await sendRequest(msg); v0.5.3 used WebSocket push events (`ChannelUpdate`, `BalanceUpdate`). v1.0.0 uses polling. The compat layer provides `EventPoller`: ```typescript -import { EventPoller } from '@erc7824/nitrolite-compat'; +import { EventPoller } from '@yellow-org/sdk-compat'; const poller = new EventPoller(client, { onChannelUpdate: (channels) => updateUI(channels), diff --git a/sdk/ts-compat/docs/migration-overview.md b/sdk/ts-compat/docs/migration-overview.md index 614451b0c..a90453983 100644 --- a/sdk/ts-compat/docs/migration-overview.md +++ b/sdk/ts-compat/docs/migration-overview.md @@ -11,17 +11,17 @@ The compat layer centralises this complexity into **~5 file changes** per app. I ## 2. Installation ```bash -npm install @erc7824/nitrolite-compat +npm install @yellow-org/sdk-compat # Peer dependencies -npm install @erc7824/nitrolite viem +npm install @yellow-org/sdk viem ``` ## 3. Import Swap | Before (v0.5.3) | After (compat) | |-----------------|----------------| -| `import { createGetChannelsMessage, parseGetChannelsResponse } from '@erc7824/nitrolite'` | `import { NitroliteClient } from '@erc7824/nitrolite-compat'` | -| Types: `AppSession`, `LedgerChannel`, `RPCAppDefinition` | Same types — re-exported from `@erc7824/nitrolite-compat` | +| `import { createGetChannelsMessage, parseGetChannelsResponse } from '@layer-3/nitrolite'` | `import { NitroliteClient } from '@yellow-org/sdk-compat'` | +| Types: `AppSession`, `LedgerChannel`, `RPCAppDefinition` | Same types — re-exported from `@yellow-org/sdk-compat` | For **types**, just change the package name. For **functions**, switch to client methods instead of `create*Message` / `parse*Response`. @@ -62,7 +62,7 @@ const channels = await client.getChannels(); ## 7. Quick Start Example ```typescript -import { NitroliteClient, WalletStateSigner, blockchainRPCsFromEnv } from '@erc7824/nitrolite-compat'; +import { NitroliteClient, WalletStateSigner, blockchainRPCsFromEnv } from '@yellow-org/sdk-compat'; // Create client (replaces new Client(ws, signer)) const client = await NitroliteClient.create({ diff --git a/sdk/ts-compat/examples/lifecycle.ts b/sdk/ts-compat/examples/lifecycle.ts index 448e4fd92..4731296a0 100644 --- a/sdk/ts-compat/examples/lifecycle.ts +++ b/sdk/ts-compat/examples/lifecycle.ts @@ -1,7 +1,7 @@ /** * Compat Layer Comprehensive Lifecycle Example * - * Exercises every public API offered by @erc7824/nitrolite-compat: + * Exercises every public API offered by @yellow-org/sdk-compat: * - NitroliteClient: creation, ping, config, assets, balances, channels, * transfers, app sessions (create/submit/close), asset helpers, error classification * - EventPoller: start/stop with callbacks @@ -12,7 +12,7 @@ * * Prerequisites: * - PRIVATE_KEY env var set to an Ethereum private key - * - CLEARNODE_WS_URL env var (defaults to wss://clearnode-v1-rc.yellow.org/ws) + * - CLEARNODE_WS_URL env var (defaults to wss://clearnode-sandbox.yellow.org/v1/ws) * - CHAIN_ID env var (defaults to 11155111 for Sepolia) * * Balance-dependent tests: @@ -100,7 +100,7 @@ async function walletSign(wc: any, hash: Hex): Promise { } const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`; -const WS_URL = process.env.CLEARNODE_WS_URL || 'wss://clearnode-v1-rc.yellow.org/ws'; +const WS_URL = process.env.CLEARNODE_WS_URL || 'wss://clearnode-sandbox.yellow.org/v1/ws'; const CHAIN_ID = parseInt(process.env.CHAIN_ID || '11155111', 10); if (Number.isNaN(CHAIN_ID)) { console.error('CHAIN_ID must be a valid integer'); @@ -574,7 +574,7 @@ async function main() { RPCMethod.CloseAppSession, RPCMethod.SubmitAppState, RPCMethod.GetAppDefinition, RPCMethod.AuthRequest, RPCMethod.AuthChallenge, RPCMethod.AuthVerify, - RPCMethod.GetLedgerTransactions, RPCMethod.GetUserTag, + RPCMethod.GetLedgerTransactions, ]; ok('RPCMethod enum', `${methods.length} methods accessible`); } catch (e: any) { fail('RPCMethod enum', e); } diff --git a/sdk/ts-compat/examples/output.txt b/sdk/ts-compat/examples/output.txt index b8cf1c46e..d7563d30b 100644 --- a/sdk/ts-compat/examples/output.txt +++ b/sdk/ts-compat/examples/output.txt @@ -1,7 +1,7 @@ === Compat Layer Comprehensive Lifecycle === Wallet: 0x -Clearnode: wss://clearnode-v1-rc.yellow.org/ws +Clearnode: wss://clearnode-sandbox.yellow.org/v1/ws Chain: 11155111 -- 1. Client Initialization -- diff --git a/sdk/ts-compat/examples/tsconfig.json b/sdk/ts-compat/examples/tsconfig.json index 27e0e3a03..3f37c3e4a 100644 --- a/sdk/ts-compat/examples/tsconfig.json +++ b/sdk/ts-compat/examples/tsconfig.json @@ -7,8 +7,8 @@ "skipLibCheck": true, "strict": true, "paths": { - "@erc7824/nitrolite": ["../../ts/src/index.ts"], - "@erc7824/nitrolite-compat": ["../src/index.ts"] + "@yellow-org/sdk": ["../../ts/src/index.ts"], + "@yellow-org/sdk-compat": ["../src/index.ts"] } }, "include": ["./*.ts"] diff --git a/sdk/ts-compat/package.json b/sdk/ts-compat/package.json index 0e59db3c3..b557d5a94 100644 --- a/sdk/ts-compat/package.json +++ b/sdk/ts-compat/package.json @@ -1,6 +1,6 @@ { "name": "@yellow-org/sdk-compat", - "version": "1.1.1", + "version": "1.2.0", "description": "Compatibility layer bridging Nitrolite SDK v0.5.3 API to v1.0.0, minimising migration effort for existing dApps.", "type": "module", "sideEffects": false, @@ -18,7 +18,7 @@ }, "keywords": [ "yellow", - "erc7824", + "layer-3", "nitrolite", "compat", "migration", @@ -28,14 +28,14 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/erc7824/nitrolite.git", + "url": "https://github.com/layer-3/nitrolite.git", "directory": "sdk/ts-compat" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { - "@yellow-org/sdk": ">=1.0.0", + "@yellow-org/sdk": ">=1.2.0", "viem": "^2.0.0" }, "dependencies": { diff --git a/sdk/ts-compat/src/client.ts b/sdk/ts-compat/src/client.ts index c9722dc8f..b6d7134bb 100644 --- a/sdk/ts-compat/src/client.ts +++ b/sdk/ts-compat/src/client.ts @@ -10,10 +10,12 @@ import type { AppParticipantV1, AppAllocationV1, AppSessionKeyStateV1, + AppInfoV1, ChannelSessionKeyStateV1, } from '@yellow-org/sdk'; +import type * as core from '@yellow-org/sdk'; import Decimal from 'decimal.js'; -import { Address, Hex, WalletClient, formatUnits, parseUnits } from 'viem'; +import { Address, Hex, WalletClient, createPublicClient, http, formatUnits, parseUnits } from 'viem'; import type { RPCBalance, @@ -42,6 +44,7 @@ import { AllowanceError, InsufficientFundsError, NotInitializedError, + OngoingStateTransitionError, UserRejectedError, } from './errors'; @@ -134,11 +137,15 @@ export class NitroliteClient { private _lastChannels: LedgerChannel[] = []; private _lastAppSessionsListError: string | null = null; private _lastAppSessionsListErrorLogged: string | null = null; + private _blockchains: core.Blockchain[] | null = null; + private _lockingTokenDecimals = new Map(); + private _blockchainRPCs: Record; - private constructor(client: Client, userAddress: Address, chainId: number) { + private constructor(client: Client, userAddress: Address, chainId: number, blockchainRPCs?: Record) { this.innerClient = client; this.userAddress = userAddress; this._chainId = BigInt(chainId); + this._blockchainRPCs = blockchainRPCs ?? {}; } // ----------------------------------------------------------------------- @@ -177,7 +184,7 @@ export class NitroliteClient { const v1Client = await Client.create(config.wsURL, stateSigner, txSigner, ...opts); - const compat = new NitroliteClient(v1Client, walletAddress, config.chainId); + const compat = new NitroliteClient(v1Client, walletAddress, config.chainId, config.blockchainRPCs); try { await compat.refreshAssets(); @@ -306,6 +313,7 @@ export class NitroliteClient { if (lower.includes('user rejected') || lower.includes('user denied')) return new UserRejectedError(msg); if (lower.includes('insufficient funds') || lower.includes('exceeds balance')) return new InsufficientFundsError(msg); if (lower.includes('not initialized') || lower.includes('not connected')) return new NotInitializedError(msg); + if (lower.includes('ongoing') || lower.includes('state transition')) return new OngoingStateTransitionError(msg); return error instanceof Error ? error : new Error(msg); } @@ -680,6 +688,7 @@ export class NitroliteClient { definitionOrParams: RPCAppDefinition | CreateAppSessionRequestParams, allocations?: RPCAppSessionAllocation[], quorumSigs?: string[], + opts?: { ownerSig?: string }, ): Promise<{ appSessionId: string; version: string; status: string }> { const def = 'definition' in definitionOrParams ? definitionOrParams.definition : definitionOrParams; const allocs = 'definition' in definitionOrParams ? definitionOrParams.allocations : (allocations ?? []); @@ -689,9 +698,12 @@ export class NitroliteClient { const sessionData = 'definition' in definitionOrParams ? (definitionOrParams.session_data ?? JSON.stringify({ allocations: allocs })) : JSON.stringify({ allocations: allocs }); + const ownerSig = 'definition' in definitionOrParams + ? (definitionOrParams.owner_sig ?? opts?.ownerSig) + : opts?.ownerSig; const v1Def: AppDefinitionV1 = { - application: def.application ?? (def as any).protocol ?? '', + applicationId: def.application || '', participants: def.participants.map((addr, i) => ({ walletAddress: addr as Address, signatureWeight: def.weights[i] ?? 1, @@ -700,7 +712,8 @@ export class NitroliteClient { nonce: BigInt(def.nonce ?? Date.now()), }; - const result = await this.innerClient.createAppSession(v1Def, sessionData, quorumSignatures); + const v1Opts = ownerSig ? { ownerSig } : undefined; + const result = await this.innerClient.createAppSession(v1Def, sessionData, quorumSignatures, v1Opts); return { appSessionId: result.appSessionId, version: result.version, status: result.status }; } @@ -755,7 +768,7 @@ export class NitroliteClient { async getAppDefinition(appSessionId: string): Promise { const def = await this.innerClient.getAppDefinition(appSessionId); return { - protocol: def.application, + protocol: def.applicationId, participants: def.participants.map((p) => p.walletAddress), weights: def.participants.map((p) => p.signatureWeight), quorum: def.quorum, @@ -931,4 +944,167 @@ export class NitroliteClient { async ping(): Promise { await this.innerClient.ping(); } + + waitForClose(): Promise { + return this.innerClient.waitForClose(); + } + + // ----------------------------------------------------------------------- + // Acknowledge + // ----------------------------------------------------------------------- + + async acknowledge(tokenAddress: Address): Promise { + const { symbol } = await this.resolveToken(tokenAddress); + await this.innerClient.acknowledge(symbol); + } + + // ----------------------------------------------------------------------- + // Token allowance + // ----------------------------------------------------------------------- + + async checkTokenAllowance(chainId: number, tokenAddress: Address): Promise { + return this.innerClient.checkTokenAllowance( + BigInt(chainId), + tokenAddress, + this.userAddress, + ); + } + + // ----------------------------------------------------------------------- + // Additional queries + // ----------------------------------------------------------------------- + + async getBlockchains(): Promise { + return this.ensureBlockchains(); + } + + private async ensureBlockchains(): Promise { + if (!this._blockchains) { + this._blockchains = await this.innerClient.getBlockchains(); + } + return this._blockchains; + } + + async getActionAllowances(wallet?: Address): Promise { + return this.innerClient.getActionAllowances(wallet ?? this.userAddress); + } + + async getEscrowChannel(escrowChannelId: string): Promise { + return this.innerClient.getEscrowChannel(escrowChannelId); + } + + // ----------------------------------------------------------------------- + // App registry + // ----------------------------------------------------------------------- + + async getApps(options?: { + appId?: string; + ownerWallet?: string; + page?: number; + pageSize?: number; + }): Promise<{ apps: AppInfoV1[]; metadata: core.PaginationMetadata }> { + return this.innerClient.getApps(options); + } + + async registerApp( + appID: string, + metadata: string, + creationApprovalNotRequired: boolean, + ): Promise { + await this.innerClient.registerApp(appID, metadata, creationApprovalNotRequired); + } + + // ----------------------------------------------------------------------- + // Security token locking + // ----------------------------------------------------------------------- + + async lockSecurityTokens( + targetWallet: Address, + chainId: number, + amount: bigint, + ): Promise { + if (amount <= 0n) throw new Error('amount must be positive'); + const decimals = await this.getLockingTokenDecimals(chainId); + const humanAmount = this.toHumanAmount(amount, decimals); + return this.innerClient.escrowSecurityTokens( + targetWallet, + BigInt(chainId), + humanAmount, + ); + } + + async initiateSecurityTokensWithdrawal(chainId: number): Promise { + return this.innerClient.initiateSecurityTokensWithdrawal(BigInt(chainId)); + } + + async cancelSecurityTokensWithdrawal(chainId: number): Promise { + return this.innerClient.cancelSecurityTokensWithdrawal(BigInt(chainId)); + } + + async withdrawSecurityTokens( + chainId: number, + destination: Address, + ): Promise { + return this.innerClient.withdrawSecurityTokens(BigInt(chainId), destination); + } + + async approveSecurityToken(chainId: number, amount: bigint): Promise { + if (amount <= 0n) throw new Error('amount must be positive'); + const decimals = await this.getLockingTokenDecimals(chainId); + const humanAmount = this.toHumanAmount(amount, decimals); + return this.innerClient.approveSecurityToken(BigInt(chainId), humanAmount); + } + + async getLockedBalance(chainId: number, wallet?: Address): Promise { + const balance = await this.innerClient.getLockedBalance( + BigInt(chainId), + wallet ?? this.userAddress, + ); + const decimals = await this.getLockingTokenDecimals(chainId); + return BigInt(balance.mul(new Decimal(10).pow(decimals)).toFixed(0)); + } + + private static readonly LOCKING_ASSET_ABI = [ + { type: 'function', name: 'asset', inputs: [], outputs: [{ type: 'address' }], stateMutability: 'view' }, + ] as const; + + private static readonly ERC20_DECIMALS_ABI = [ + { type: 'function', name: 'decimals', inputs: [], outputs: [{ type: 'uint8' }], stateMutability: 'view' }, + ] as const; + + private async getLockingTokenDecimals(chainId: number): Promise { + const cached = this._lockingTokenDecimals.get(chainId); + if (cached !== undefined) return cached; + + const blockchains = await this.ensureBlockchains(); + const chain = blockchains.find((b) => b.id === BigInt(chainId)); + if (!chain?.lockingContractAddress) { + throw new Error(`No locking contract configured for chain ${chainId}`); + } + + const rpcUrl = this._blockchainRPCs[chainId]; + if (!rpcUrl) { + throw new Error( + `No RPC URL configured for chain ${chainId}. ` + + 'Pass blockchainRPCs in NitroliteClientConfig to use locking methods.', + ); + } + + const publicClient = createPublicClient({ transport: http(rpcUrl) }); + + const tokenAddress = await publicClient.readContract({ + address: chain.lockingContractAddress, + abi: NitroliteClient.LOCKING_ASSET_ABI, + functionName: 'asset', + }) as Address; + + const decimals = await publicClient.readContract({ + address: tokenAddress, + abi: NitroliteClient.ERC20_DECIMALS_ABI, + functionName: 'decimals', + }) as number; + + this._lockingTokenDecimals.set(chainId, decimals); + return decimals; + } } diff --git a/sdk/ts-compat/src/index.ts b/sdk/ts-compat/src/index.ts index 9c6d1729c..469f76c6e 100644 --- a/sdk/ts-compat/src/index.ts +++ b/sdk/ts-compat/src/index.ts @@ -1,8 +1,8 @@ // ============================================================================= -// @erc7824/nitrolite-compat barrel export +// @yellow-org/sdk-compat barrel export // -// Re-exports everything apps previously imported from '@erc7824/nitrolite' -// (v0.5.3) but backed by the v1.0.0 SDK. +// Re-exports everything apps previously imported from '@layer-3/nitrolite' +// (v0.5.3) but backed by the v1.0.0+ SDK (@yellow-org/sdk). // ============================================================================= // --- Client facade --- @@ -123,6 +123,7 @@ export { UserRejectedError, InsufficientFundsError, NotInitializedError, + OngoingStateTransitionError, getUserFacingMessage, } from './errors'; @@ -133,7 +134,7 @@ export { EventPoller, type EventPollerCallbacks } from './events'; export { buildClientOptions, blockchainRPCsFromEnv, type CompatClientConfig } from './config'; // NOTE: SDK classes (Client, ChannelDefaultSigner, etc.) are intentionally NOT -// re-exported here. Barrel re-exports from '@erc7824/nitrolite' trigger eager +// re-exported here. Barrel re-exports from '@yellow-org/sdk' trigger eager // module evaluation of the full SDK, which has side effects that throw during // SSR / module-load time. Apps needing those classes should import directly -// from '@erc7824/nitrolite'. +// from '@yellow-org/sdk'. diff --git a/sdk/ts-compat/src/types.ts b/sdk/ts-compat/src/types.ts index e895d0038..857f31a7d 100644 --- a/sdk/ts-compat/src/types.ts +++ b/sdk/ts-compat/src/types.ts @@ -29,7 +29,6 @@ export enum RPCMethod { AuthVerify = 'auth_verify', Error = 'error', GetLedgerTransactions = 'get_ledger_transactions', - GetUserTag = 'get_user_tag', TransferNotification = 'tr', } @@ -199,14 +198,15 @@ export interface CloseAppSessionRequestParams { allocations: RPCAppSessionAllocation[]; version?: number; session_data?: string; - quorum_sigs?: string[]; + quorum_sigs: string[]; } export interface CreateAppSessionRequestParams { definition: RPCAppDefinition; allocations: RPCAppSessionAllocation[]; session_data?: string; - quorum_sigs?: string[]; + quorum_sigs: string[]; + owner_sig?: string; } export interface SubmitAppStateRequestParamsV02 { diff --git a/sdk/ts/.prettierrc b/sdk/ts/.prettierrc index d32fbc15f..8b86bdcbb 100644 --- a/sdk/ts/.prettierrc +++ b/sdk/ts/.prettierrc @@ -1,6 +1,7 @@ { "jsxBracketSameLine": true, "singleQuote": true, + "tabWidth": 2, "overrides": [ { "files": ["**/*.css", "**/*.scss", "**/*.pcss", "**/*.html"], @@ -11,8 +12,7 @@ } ], "jsxSingleQuote": false, - "printWidth": 120, - "tabWidth": 4, + "printWidth": 100, "endOfLine": "auto", "semi": true, "tslintIntegration": true, diff --git a/sdk/ts/README.md b/sdk/ts/README.md index 95bef03b5..fa42f0edf 100644 --- a/sdk/ts/README.md +++ b/sdk/ts/README.md @@ -1,7 +1,7 @@ # Yellow TypeScript SDK [![npm version](https://img.shields.io/npm/v/@yellow-org/sdk.svg)](https://www.npmjs.com/package/@yellow-org/sdk) -[![License](https://img.shields.io/npm/l/@yellow-org/sdk.svg)](https://github.com/erc7824/nitrolite/blob/main/LICENSE) +[![License](https://img.shields.io/npm/l/@yellow-org/sdk.svg)](https://github.com/layer-3/nitrolite/blob/main/LICENSE) TypeScript SDK for Clearnode payment channels providing both high-level and low-level operations in a unified client: - **State Operations**: `deposit()`, `withdraw()`, `transfer()`, `closeHomeChannel()`, `acknowledge()` - build and co-sign states off-chain @@ -9,7 +9,7 @@ TypeScript SDK for Clearnode payment channels providing both high-level and low- - **Low-Level Operations**: Direct RPC access for custom flows and advanced use cases - **Full Feature Parity**: 100% compatibility with Go SDK functionality -> If you are migrating from `@erc7824/nitrolite@v0.5.3`, please consider using the [@yellow-org/sdk-compat](https://www.npmjs.com/package/@yellow-org/sdk-compat) package. It is a translation layer that uses this SDK underneath and maps the familiar v0.5.3 API surfaces to this SDK. +> If you are migrating from `@layer-3/nitrolite@v0.5.3`, please consider using the [@yellow-org/sdk-compat](https://www.npmjs.com/package/@yellow-org/sdk-compat) package. It is a translation layer that uses this SDK underneath and maps the familiar v0.5.3 API surfaces to this SDK. ## Method Cheat Sheet @@ -52,11 +52,18 @@ client.getEscrowChannel(escrowChannelId) // Escrow channel info client.getLatestState(wallet, asset, onlySigned) // Latest state ``` +### App Registry +```typescript +client.getApps(opts) // List registered apps +client.registerApp(appID, metadata, approvalNotRequired) // Register new app +``` + ### App Sessions ```typescript client.getAppSessions(opts) // List sessions client.getAppDefinition(appSessionId) // Session definition client.createAppSession(definition, sessionData, sigs) // Create session +client.createAppSession(def, data, sigs, { ownerSig }) // Create with owner approval client.submitAppSessionDeposit(update, sigs, asset, amount) // Deposit to session client.submitAppState(update, sigs) // Update session client.rebalanceAppSessions(signedUpdates) // Atomic rebalance @@ -145,7 +152,7 @@ main().catch(console.error); ``` sdk/ts/src/ ├── client.ts # Core client, constructors, high-level operations -├── signers.ts # EthereumMsgSigner and EthereumRawSigner +├── signers.ts # Signers (EthereumMsg/Raw, Channel, AppSession) ├── config.ts # Configuration options ├── asset_store.ts # Asset metadata caching ├── utils.ts # Type transformations @@ -391,6 +398,21 @@ const state = await client.getLatestState(wallet, asset, onlySigned); **Note:** State submission and channel creation are handled internally by state operations (`deposit()`, `withdraw()`, `transfer()`). On-chain settlement is handled by `checkpoint()`. +### App Registry + +```typescript +// List registered applications with optional filtering +const { apps, metadata } = await client.getApps({ + appId: 'my-app', + ownerWallet: '0x1234...', + page: 1, + pageSize: 10, +}); + +// Register a new application +await client.registerApp('my-app', '{"name": "My App"}', false); +``` + ### App Sessions (Low-Level) ```typescript @@ -417,6 +439,50 @@ await client.submitAppState(appUpdate, quorumSigs); const batchId = await client.rebalanceAppSessions(signedUpdates); ``` +#### Owner Approval for App Session Creation + +When an app is registered with `creationApprovalNotRequired: false`, the app owner must sign the session creation request. Pass the owner's signature via the options parameter: + +```typescript +import { AppSessionWalletSignerV1, EthereumMsgSigner } from '@yellow-org/sdk'; + +// Owner signs the create request using their app session signer +const ownerMsgSigner = new EthereumMsgSigner(ownerPrivateKey); +const ownerAppSessionSigner = new AppSessionWalletSignerV1(ownerMsgSigner); +const ownerSig = await ownerAppSessionSigner.signMessage(createRequest); + +const { appSessionId } = await client.createAppSession( + definition, + sessionData, + quorumSigs, + { ownerSig } +); +``` + +### App Session Signers + +App session operations require signatures with a type byte prefix, similar to channel signers: + +| Type | Byte | Class | Usage | +|------|------|-------|-------| +| Wallet | `0xA1` | `AppSessionWalletSignerV1` | Main wallet signs app session operations | +| Session Key | `0xA2` | `AppSessionKeySignerV1` | Delegated session key signs on behalf of wallet | + +```typescript +import { EthereumMsgSigner, AppSessionWalletSignerV1, AppSessionKeySignerV1 } from '@yellow-org/sdk'; + +// Create app session wallet signer +const msgSigner = new EthereumMsgSigner(privateKey); +const appSessionSigner = new AppSessionWalletSignerV1(msgSigner); + +// Sign app session operations (create, deposit, state updates, etc.) +const sig = await appSessionSigner.signMessage(packedRequest); + +// For session key delegation +const sessionKeyMsgSigner = new EthereumMsgSigner(sessionKeyPrivateKey); +const sessionKeySigner = new AppSessionKeySignerV1(sessionKeyMsgSigner); +``` + ### App Session Keys ```typescript @@ -734,8 +800,17 @@ queryTransactions().catch(console.error); ### Example 4: App Session Workflow +For a comprehensive multi-party app session example including app registration, owner approval, +session key delegation, deposits, redistribution, and more, see +[examples/app_sessions/lifecycle.ts](examples/app_sessions/lifecycle.ts). + ```typescript -import { Client, createSigners, withBlockchainRPC } from '@yellow-org/sdk'; +import { + Client, createSigners, EthereumMsgSigner, + AppSessionWalletSignerV1, withBlockchainRPC +} from '@yellow-org/sdk'; +import { AppStateUpdateIntent } from '@yellow-org/sdk/app/types'; +import { packCreateAppSessionRequestV1, packAppStateUpdateV1 } from '@yellow-org/sdk/app/packing'; import Decimal from 'decimal.js'; async function appSessionExample() { @@ -748,42 +823,55 @@ async function appSessionExample() { ); try { + // Register app (required before creating sessions) + await client.registerApp('chess-v1', '{}', true); + + // Create app session signer + const msgSigner = new EthereumMsgSigner(process.env.PRIVATE_KEY!); + const appSessionSigner = new AppSessionWalletSignerV1(msgSigner); + // Create app session const definition = { - application: 'chess-v1', + applicationId: 'chess-v1', participants: [ - { walletAddress: client.getUserAddress(), signatureWeight: 1 }, - { walletAddress: '0xOpponent...', signatureWeight: 1 }, + { walletAddress: client.getUserAddress(), signatureWeight: 100 }, ], - quorum: 2, - nonce: 1n, + quorum: 100, + nonce: BigInt(Date.now() * 1000000), }; + const createRequest = packCreateAppSessionRequestV1(definition, '{}'); + const createSig = await appSessionSigner.signMessage(createRequest); + const { appSessionId } = await client.createAppSession( definition, '{}', - ['sig1', 'sig2'] + [createSig] ); console.log('Session created:', appSessionId); // Deposit to app session + const depositAmount = new Decimal(50); const appUpdate = { appSessionId, - intent: 1, // Deposit - version: 1n, + intent: AppStateUpdateIntent.Deposit, + version: 2n, allocations: [{ participant: client.getUserAddress(), asset: 'usdc', - amount: new Decimal(50), + amount: depositAmount, }], sessionData: '{}', }; + const depositRequest = packAppStateUpdateV1(appUpdate); + const depositSig = await appSessionSigner.signMessage(depositRequest); + const nodeSig = await client.submitAppSessionDeposit( appUpdate, - ['sig1'], + [depositSig], 'usdc', - new Decimal(50) + depositAmount ); console.log('Deposit signature:', nodeSig); @@ -854,6 +942,20 @@ import type { ChannelSessionKeyStateV1, PaginationMetadata, } from '@yellow-org/sdk'; + +// Signer types +import { + EthereumMsgSigner, + EthereumRawSigner, + ChannelDefaultSigner, + ChannelSessionKeyStateSigner, + AppSessionWalletSignerV1, + AppSessionKeySignerV1, + createSigners, +} from '@yellow-org/sdk'; + +// App Registry types (from rpc/types) +import type { AppV1, AppInfoV1 } from '@yellow-org/sdk'; ``` ### BigInt for Chain IDs @@ -976,8 +1078,8 @@ Part of the Nitrolite project. See [LICENSE](../../LICENSE) for details. ## Related Projects - [Nitrolite TS Compat](https://www.npmjs.com/package/@yellow-org/sdk-compat) - Compatibility layer for older TypeScript versions -- [Nitrolite Go SDK](https://github.com/erc7824/nitrolite/tree/stable/sdk/go) - Go implementation with same API -- [Nitrolite Smart Contracts](https://github.com/erc7824/nitrolite/tree/stable/contracts) - On-chain contracts +- [Nitrolite Go SDK](https://github.com/layer-3/nitrolite/tree/stable/sdk/go) - Go implementation with same API +- [Nitrolite Smart Contracts](https://github.com/layer-3/nitrolite/tree/stable/contracts) - On-chain contracts --- diff --git a/sdk/ts/docs/TYPECHAIN_AUTOMATION.md b/sdk/ts/docs/TYPECHAIN_AUTOMATION.md index 51b89426e..e7c53b201 100644 --- a/sdk/ts/docs/TYPECHAIN_AUTOMATION.md +++ b/sdk/ts/docs/TYPECHAIN_AUTOMATION.md @@ -55,7 +55,7 @@ export default defineConfig({ ### 3. Usage in Code ```typescript -import { custodyAbi } from '@erc7824/nitrolite'; +import { custodyAbi } from '@layer-3/nitrolite'; // ✅ Full type safety and autocomplete const result = await publicClient.readContract({ diff --git a/sdk/ts/examples/app_sessions/README.md b/sdk/ts/examples/app_sessions/README.md index 2afd7ea8b..819f1f751 100644 --- a/sdk/ts/examples/app_sessions/README.md +++ b/sdk/ts/examples/app_sessions/README.md @@ -15,7 +15,7 @@ This example demonstrates the complete lifecycle of Nitrolite app sessions, incl - Node.js 18+ installed - Three wallets with private keys -- Access to a Nitrolite node (default: `wss://deployment.yellow.org/ws`) +- Access to a Nitrolite node (default: `wss://clearnode-sandbox.yellow.org/v1/ws`) ## Setup diff --git a/sdk/ts/examples/app_sessions/lifecycle.ts b/sdk/ts/examples/app_sessions/lifecycle.ts index 0ec04893e..55cbbc60d 100644 --- a/sdk/ts/examples/app_sessions/lifecycle.ts +++ b/sdk/ts/examples/app_sessions/lifecycle.ts @@ -1,47 +1,66 @@ /** * Example: Complete App Session Lifecycle * + * Prerequisites (minimum channel balances): + * - Wallet 1: 0.0001 USDC + * - Wallet 2: 0.00015 WETH + * - Wallet 3: no balance required (receives funds via redistribution) + * * This example demonstrates: - * 1. Create first app session for wallet 1 - * 2. Deposit USDC into first app session by wallet 1 - * 3. Create second app session for wallet 2 with wallet 3 as a participant - * 4. Deposit WETH into second app session by wallet 2 - * 5. Redistribute app state within app session so that participant with wallet 3 also has some allocation - * 6. Rebalance 2 app sessions atomically + * 1. Register apps in the app registry (required before creating app sessions) + * 2. Create first app session for wallet 1 + * 3. Deposit USDC into first app session by wallet 1 + * 4. Create second app session for wallet 2 with wallet 3 as a participant + * 5. Deposit WETH into second app session by wallet 2 + * 6. Redistribute app state within app session so that participant with wallet 3 also has some allocation * 7. Wallet 3 withdraws from his app session * 8. Close both app sessions + * 9. Fail case: attempt to create app session for unregistered app (expected to fail) */ import Decimal from 'decimal.js'; +import { Hex } from 'viem'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { Client } from '../../src/client'; -import { createSigners } from '../../src/signers'; +import { + createSigners, + EthereumMsgSigner, + AppSessionWalletSignerV1, + AppSessionKeySignerV1, +} from '../../src/signers'; import { AppDefinitionV1, AppStateUpdateV1, AppStateUpdateIntent, - SignedAppStateUpdateV1, + AppSessionKeyStateV1, } from '../../src/app/types'; -import { packCreateAppSessionRequestV1, packAppStateUpdateV1 } from '../../src/app/packing'; +import { packCreateAppSessionRequestV1, packAppStateUpdateV1, packAppSessionKeyStateV1 } from '../../src/app/packing'; async function main() { // Replace with a real deployment url - const wsURL = 'wss://deployment.yellow.org/ws'; + const wsURL = 'wss://clearnode-sandbox.yellow.org/v1/ws'; // --- 0. Setup Wallets --- // Replace these strings with your actual hex private keys - const wallet1PrivateKey = '0x7d607...'; - const wallet2PrivateKey = '0x9b652...'; - const wallet3PrivateKey = '0xf6369...'; + const wallet1PrivateKey = '0x7d607...' as Hex; + const wallet2PrivateKey = '0x9b652...' as Hex; + const wallet3PrivateKey = '0xf6369...' as Hex; // Create signers from private keys const wallet1Signers = createSigners(wallet1PrivateKey); const wallet2Signers = createSigners(wallet2PrivateKey); const wallet3Signers = createSigners(wallet3PrivateKey); + // Create app session wallet signers (prepend 0x00 type byte) + const wallet1MsgSigner = new EthereumMsgSigner(wallet1PrivateKey); + const wallet2MsgSigner = new EthereumMsgSigner(wallet2PrivateKey); + const appSession1Signer = new AppSessionWalletSignerV1(wallet1MsgSigner); + const appSession2Signer = new AppSessionWalletSignerV1(wallet2MsgSigner); + // Extract wallet addresses - const wallet1Address = wallet1Signers.stateSigner.getAddress(); // 0x053aEAD7d3eebE4359300fDE849bCD9E77384989 - const wallet2Address = wallet2Signers.stateSigner.getAddress(); // 0x2BfA10aAd64Ae0F7855f54f27117Fcc9C61C6770 - const wallet3Address = wallet3Signers.stateSigner.getAddress(); // 0xaB5670b44cb4A3B5535BD637cb600DA572148c98 + const wallet1Address = wallet1Signers.stateSigner.getAddress(); + const wallet2Address = wallet2Signers.stateSigner.getAddress(); + const wallet3Address = wallet3Signers.stateSigner.getAddress(); console.log('--- Wallets Imported ---'); console.log(`Wallet 1 Address: ${wallet1Address}`); @@ -55,31 +74,52 @@ async function main() { wallet1Signers.stateSigner, wallet1Signers.txSigner ); + const wallet2Client = await Client.create( + wsURL, + wallet2Signers.stateSigner, + wallet2Signers.txSigner + ); + const wallet3Client = await Client.create( + wsURL, + wallet3Signers.stateSigner, + wallet3Signers.txSigner + ); + + // --- 1. Register Apps --- + console.log('=== Step 1: Registering Apps ==='); - // --- 1. Create App Session 1 (Single Participant: Wallet 1) --- - console.log('=== Step 1: Creating App Session 1 (Wallet 1 only) ==='); + const suffix = String(Math.floor(Math.random() * 1000000)).padStart(6, '0'); + const app1ID = `test-app-${suffix}`; + const app2ID = `multi-party-app-${suffix}`; + + await wallet1Client.registerApp(app1ID, '{}', true); + console.log(`✓ Registered app: ${app1ID}`); + + await wallet1Client.registerApp(app2ID, '{}', false); + console.log(`✓ Registered app: ${app2ID} (owner approval required)\n`); + + // --- 2. Create App Session 1 (Single Participant: Wallet 1) --- + console.log('=== Step 2: Creating App Session 1 (Wallet 1 only) ==='); const session1Definition: AppDefinitionV1 = { - application: 'test-app', + applicationId: app1ID, participants: [{ walletAddress: wallet1Address, signatureWeight: 100 }], quorum: 100, - nonce: BigInt(Date.now() * 1000000), // Use nanoseconds like Go + nonce: BigInt(Date.now() * 1000000), }; const session1CreateRequest = packCreateAppSessionRequestV1(session1Definition, '{}'); - const wallet1CreateSession1Sig = await wallet1Signers.stateSigner.signMessage( - session1CreateRequest - ); + const appSession1CreateSig = await appSession1Signer.signMessage(session1CreateRequest); const { appSessionId: session1ID } = await wallet1Client.createAppSession( session1Definition, '{}', - [wallet1CreateSession1Sig] + [appSession1CreateSig] ); console.log(`✓ Created App Session 1: ${session1ID}\n`); - // --- 2. Deposit USDC into Session 1 --- - console.log('=== Step 2: Depositing USDC into Session 1 ==='); + // --- 3. Deposit USDC into Session 1 --- + console.log('=== Step 3: Depositing USDC into Session 1 ==='); const session1DepositAmount = new Decimal(0.0001); const session1DepositUpdate: AppStateUpdateV1 = { @@ -93,12 +133,12 @@ async function main() { }; const session1DepositRequest = packAppStateUpdateV1(session1DepositUpdate); - const wallet1DepositSig = await wallet1Signers.stateSigner.signMessage(session1DepositRequest); + const appSession1DepositSig = await appSession1Signer.signMessage(session1DepositRequest); try { await wallet1Client.submitAppSessionDeposit( session1DepositUpdate, - [wallet1DepositSig], + [appSession1DepositSig], 'usdc', session1DepositAmount ); @@ -107,17 +147,39 @@ async function main() { console.log(`⚠ Deposit warning: ${err}`); } - // --- 3. Create App Session 2 (Multi-Party: Wallet 2 & 3) --- - console.log('=== Step 3: Creating App Session 2 (Wallet 2 & 3) ==='); + // --- 4. Create App Session 2 (Multi-Party: Wallet 2 & 3) --- + console.log('=== Step 4: Creating App Session 2 (Wallet 2 & 3) ==='); - const wallet2Client = await Client.create( - wsURL, - wallet2Signers.stateSigner, - wallet2Signers.txSigner - ); + const appID = app2ID; + + // Generate session key for wallet 3 + const sessionKey3PrivateKey = generatePrivateKey(); + const sessionKey3Account = privateKeyToAccount(sessionKey3PrivateKey); + const sessionKey3MsgSigner = new EthereumMsgSigner(sessionKey3PrivateKey); + + const expiresAt = Math.floor(Date.now() / 1000) + 10 * 60; // 10 minutes from now + + const appSessionKey3State: AppSessionKeyStateV1 = { + session_key: sessionKey3Account.address, + user_address: wallet3Address, + version: '1', + application_ids: [appID], + app_session_ids: [], + expires_at: String(expiresAt), + user_sig: '', + }; + + const packedAppSessionKey3State = packAppSessionKeyStateV1(appSessionKey3State); + const wallet3MsgSigner = new EthereumMsgSigner(wallet3PrivateKey); + const appSessionKey3StateSig = await wallet3MsgSigner.signMessage(packedAppSessionKey3State); + appSessionKey3State.user_sig = appSessionKey3StateSig; + + await wallet3Client.submitSessionKeyState(appSessionKey3State); + + const appSession3Signer = new AppSessionKeySignerV1(sessionKey3MsgSigner); const session2Definition: AppDefinitionV1 = { - application: 'multi-party-app', + applicationId: appID, participants: [ { walletAddress: wallet2Address, signatureWeight: 50 }, { walletAddress: wallet3Address, signatureWeight: 50 }, @@ -127,24 +189,24 @@ async function main() { }; const session2CreateRequest = packCreateAppSessionRequestV1(session2Definition, '{}'); - const wallet2CreateSession2Sig = await wallet2Signers.stateSigner.signMessage( - session2CreateRequest - ); - const wallet3CreateSession2Sig = await wallet3Signers.stateSigner.signMessage( - session2CreateRequest - ); + const appSession2CreateSig = await appSession2Signer.signMessage(session2CreateRequest); + const appSession3CreateSig = await appSession3Signer.signMessage(session2CreateRequest); + + // Owner approval: wallet1 is the owner of app2, sign the create request using app session signer + const ownerApprovalSig = await appSession1Signer.signMessage(session2CreateRequest); const { appSessionId: session2ID } = await wallet2Client.createAppSession( session2Definition, '{}', - [wallet2CreateSession2Sig, wallet3CreateSession2Sig] + [appSession2CreateSig, appSession3CreateSig], + { ownerSig: ownerApprovalSig } ); console.log(`✓ Created App Session 2: ${session2ID}\n`); - // --- 4. Deposit WETH into Session 2 by Wallet 2 --- - console.log('=== Step 4: Depositing WETH into Session 2 ==='); + // --- 5. Deposit WETH into Session 2 by Wallet 2 --- + console.log('=== Step 5: Depositing WETH into Session 2 ==='); - const session2DepositAmount = new Decimal(0.015); + const session2DepositAmount = new Decimal(0.00015); const session2DepositUpdate: AppStateUpdateV1 = { appSessionId: session2ID, intent: AppStateUpdateIntent.Deposit, @@ -156,12 +218,12 @@ async function main() { }; const session2DepositRequest = packAppStateUpdateV1(session2DepositUpdate); - const wallet2DepositSig = await wallet2Signers.stateSigner.signMessage(session2DepositRequest); - const wallet3DepositSig = await wallet3Signers.stateSigner.signMessage(session2DepositRequest); + const appSession2DepositSig = await appSession2Signer.signMessage(session2DepositRequest); + const appSession3DepositSig = await appSession3Signer.signMessage(session2DepositRequest); const nodeSig = await wallet2Client.submitAppSessionDeposit( session2DepositUpdate, - [wallet2DepositSig, wallet3DepositSig], + [appSession2DepositSig, appSession3DepositSig], 'weth', session2DepositAmount ); @@ -177,117 +239,43 @@ async function main() { ); } - // --- 5. Redistribute within Session 2 (Wallet 2 -> Wallet 3) --- - console.log('=== Step 5: Redistributing funds in Session 2 ==='); + // --- 6. Redistribute within Session 2 (Wallet 2 -> Wallet 3) --- + console.log('=== Step 6: Redistributing funds in Session 2 ==='); const session2RedistributeUpdate: AppStateUpdateV1 = { appSessionId: session2ID, intent: AppStateUpdateIntent.Operate, version: 3n, allocations: [ - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.01) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.005) }, + { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.0001) }, + { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00005) }, ], sessionData: '{}', }; const session2RedistributeRequest = packAppStateUpdateV1(session2RedistributeUpdate); - const wallet2RedistributeSig = await wallet2Signers.stateSigner.signMessage( + const appSession2RedistributeSig = await appSession2Signer.signMessage( session2RedistributeRequest ); - const wallet3RedistributeSig = await wallet3Signers.stateSigner.signMessage( + const appSession3RedistributeSig = await appSession3Signer.signMessage( session2RedistributeRequest ); // Multi-sig required for state transition try { await wallet2Client.submitAppState(session2RedistributeUpdate, [ - wallet2RedistributeSig, - wallet3RedistributeSig, + appSession2RedistributeSig, + appSession3RedistributeSig, ]); - console.log('✓ Redistributed WETH: Wallet 2 (0.01) -> Wallet 3 (0.005)\n'); + console.log('✓ Redistributed WETH: Wallet 2 (0.0001) -> Wallet 3 (0.00005)\n'); } catch (err) { console.error(`Redistribution failed: ${err}`); throw err; } - // --- 6. Rebalance Both App Sessions Atomically --- - console.log('=== Step 6: Atomic Rebalance Across Sessions ==='); - - // Check current allocations before rebalance - const { sessions: session1InfoBeforeRebalance } = await wallet1Client.getAppSessions({ - appSessionId: session1ID, - }); - if (session1InfoBeforeRebalance.length > 0) { - console.log( - `Session 1 before rebalance - Version: ${session1InfoBeforeRebalance[0].version}, Allocations: ${JSON.stringify(session1InfoBeforeRebalance[0].allocations)}` - ); - } - - const { sessions: session2InfoBeforeRebalance } = await wallet2Client.getAppSessions({ - appSessionId: session2ID, - }); - if (session2InfoBeforeRebalance.length > 0) { - console.log( - `Session 2 before rebalance - Version: ${session2InfoBeforeRebalance[0].version}, Allocations: ${JSON.stringify(session2InfoBeforeRebalance[0].allocations)}\n` - ); - } - - // Prepare rebalance updates for both sessions - const session1RebalanceUpdate: AppStateUpdateV1 = { - appSessionId: session1ID, - intent: AppStateUpdateIntent.Rebalance, - version: 3n, - allocations: [ - { participant: wallet1Address, asset: 'weth', amount: new Decimal(0.005) }, - { participant: wallet1Address, asset: 'usdc', amount: new Decimal(0.00005) }, - ], - sessionData: '{}', - }; - - const session1RebalanceRequest = packAppStateUpdateV1(session1RebalanceUpdate); - const wallet1RebalanceSig = await wallet1Signers.stateSigner.signMessage( - session1RebalanceRequest - ); - - const session2RebalanceUpdate: AppStateUpdateV1 = { - appSessionId: session2ID, - intent: AppStateUpdateIntent.Rebalance, - version: 4n, - allocations: [ - { participant: wallet2Address, asset: 'usdc', amount: new Decimal(0.00005) }, - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.005) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.005) }, - ], - sessionData: '{}', - }; - - const session2RebalanceRequest = packAppStateUpdateV1(session2RebalanceUpdate); - const wallet2RebalanceSig = await wallet2Signers.stateSigner.signMessage( - session2RebalanceRequest - ); - const wallet3RebalanceSig = await wallet3Signers.stateSigner.signMessage( - session2RebalanceRequest - ); - - // Submit atomic rebalance - const signedRebalanceUpdates: SignedAppStateUpdateV1[] = [ - { - appStateUpdate: session1RebalanceUpdate, - quorumSigs: [wallet1RebalanceSig], - }, - { - appStateUpdate: session2RebalanceUpdate, - quorumSigs: [wallet2RebalanceSig, wallet3RebalanceSig], - }, - ]; - - try { - const rebalanceBatchID = await wallet2Client.rebalanceAppSessions(signedRebalanceUpdates); - console.log(`✓ Atomic Rebalance Submitted. BatchID: ${rebalanceBatchID}\n`); - } catch (err) { - console.log(`⚠ Rebalance Error: ${err}\n`); - } + // NOTE: Rebalance step is disabled. + // // --- 7. Rebalance Both App Sessions Atomically --- + // ... (rebalance code omitted) ... // --- 7. Wallet 3 Withdraws from Session 2 --- console.log('=== Step 7: Wallet 3 Withdrawing from Session 2 ==='); @@ -295,29 +283,28 @@ async function main() { const session2WithdrawUpdate: AppStateUpdateV1 = { appSessionId: session2ID, intent: AppStateUpdateIntent.Withdraw, - version: 5n, + version: 4n, allocations: [ - { participant: wallet2Address, asset: 'usdc', amount: new Decimal(0.00005) }, - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.005) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.001) }, + { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.00005) }, + { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00001) }, ], sessionData: '{}', }; const session2WithdrawRequest = packAppStateUpdateV1(session2WithdrawUpdate); - const wallet2WithdrawSig = await wallet2Signers.stateSigner.signMessage( + const appSession2WithdrawSig = await appSession2Signer.signMessage( session2WithdrawRequest ); - const wallet3WithdrawSig = await wallet3Signers.stateSigner.signMessage( + const appSession3WithdrawSig = await appSession3Signer.signMessage( session2WithdrawRequest ); try { await wallet2Client.submitAppState(session2WithdrawUpdate, [ - wallet2WithdrawSig, - wallet3WithdrawSig, + appSession2WithdrawSig, + appSession3WithdrawSig, ]); - console.log('✓ Wallet 3 successfully withdrew 0.004 WETH back to channel\n'); + console.log('✓ Wallet 3 successfully withdrew WETH back to channel\n'); } catch (err) { console.log(`⚠ Withdraw Error: ${err}\n`); } @@ -329,19 +316,18 @@ async function main() { const session1CloseUpdate: AppStateUpdateV1 = { appSessionId: session1ID, intent: AppStateUpdateIntent.Close, - version: 4n, + version: 3n, allocations: [ - { participant: wallet1Address, asset: 'weth', amount: new Decimal(0.005) }, - { participant: wallet1Address, asset: 'usdc', amount: new Decimal(0.00005) }, + { participant: wallet1Address, asset: 'usdc', amount: new Decimal(0.0001) }, ], sessionData: '{}', }; const session1CloseRequest = packAppStateUpdateV1(session1CloseUpdate); - const wallet1CloseSig = await wallet1Signers.stateSigner.signMessage(session1CloseRequest); + const appSession1CloseSig = await appSession1Signer.signMessage(session1CloseRequest); try { - await wallet1Client.submitAppState(session1CloseUpdate, [wallet1CloseSig]); + await wallet1Client.submitAppState(session1CloseUpdate, [appSession1CloseSig]); console.log('✓ Session 1 successfully closed'); } catch (err) { console.log(`⚠ Close Session 1 Error: ${err}`); @@ -351,34 +337,58 @@ async function main() { const session2CloseUpdate: AppStateUpdateV1 = { appSessionId: session2ID, intent: AppStateUpdateIntent.Close, - version: 6n, + version: 5n, allocations: [ - { participant: wallet2Address, asset: 'usdc', amount: new Decimal(0.00005) }, - { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.005) }, - { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.001) }, + { participant: wallet2Address, asset: 'weth', amount: new Decimal(0.00005) }, + { participant: wallet3Address, asset: 'weth', amount: new Decimal(0.00001) }, ], sessionData: '{}', }; const session2CloseRequest = packAppStateUpdateV1(session2CloseUpdate); - const wallet2CloseSig = await wallet2Signers.stateSigner.signMessage(session2CloseRequest); - const wallet3CloseSig = await wallet3Signers.stateSigner.signMessage(session2CloseRequest); + const appSession2CloseSig = await appSession2Signer.signMessage(session2CloseRequest); + const appSession3CloseSig = await appSession3Signer.signMessage(session2CloseRequest); try { await wallet2Client.submitAppState(session2CloseUpdate, [ - wallet2CloseSig, - wallet3CloseSig, + appSession2CloseSig, + appSession3CloseSig, ]); console.log('✓ Session 2 successfully closed'); } catch (err) { console.log(`⚠ Close Session 2 Error: ${err}`); } + // --- 9. Fail Case: Create App Session for Unregistered App --- + console.log('\n=== Step 9: Creating App Session for Unregistered App (expected to fail) ==='); + + const unregisteredDefinition: AppDefinitionV1 = { + applicationId: `unregistered-app-${suffix}`, + participants: [{ walletAddress: wallet1Address, signatureWeight: 100 }], + quorum: 100, + nonce: BigInt(Date.now() * 1000000), + }; + + const unregisteredCreateRequest = packCreateAppSessionRequestV1(unregisteredDefinition, '{}'); + const unregisteredSig = await appSession1Signer.signMessage(unregisteredCreateRequest); + + try { + await wallet1Client.createAppSession( + unregisteredDefinition, + '{}', + [unregisteredSig] + ); + console.log('✗ Unexpected success: app session was created for unregistered app'); + } catch (err) { + console.log(`✓ Expected error: ${err}`); + } + console.log('\n=== Example Complete ==='); // Close clients await wallet1Client.close(); await wallet2Client.close(); + await wallet3Client.close(); // Exit successfully process.exit(0); diff --git a/sdk/ts/examples/app_sessions/package-lock.json b/sdk/ts/examples/app_sessions/package-lock.json index 91846bfde..1e0eef30d 100644 --- a/sdk/ts/examples/app_sessions/package-lock.json +++ b/sdk/ts/examples/app_sessions/package-lock.json @@ -19,7 +19,7 @@ } }, "../..": { - "name": "@erc7824/nitrolite", + "name": "@layer-3/nitrolite", "version": "1.1.0", "license": "MIT", "dependencies": { diff --git a/sdk/ts/examples/app_sessions/package.json b/sdk/ts/examples/app_sessions/package.json index 8fb08af16..ae8a52246 100644 --- a/sdk/ts/examples/app_sessions/package.json +++ b/sdk/ts/examples/app_sessions/package.json @@ -9,7 +9,7 @@ "dependencies": { "@yellow-org/sdk": "file:../..", "decimal.js": "^10.4.3", - "viem": "^2.21.54" + "viem": "^2.46.0" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/sdk/ts/examples/example-app/README.md b/sdk/ts/examples/example-app/README.md index a4db7e15d..f1d4011e9 100644 --- a/sdk/ts/examples/example-app/README.md +++ b/sdk/ts/examples/example-app/README.md @@ -1,6 +1,6 @@ # Nitrolite SDK Integration Guide -This example app demonstrates how to integrate the `@erc7824/nitrolite` TypeScript SDK into a React application. Use it as a reference for building your own Yellow-Network-powered app. +This example app demonstrates how to integrate the `@layer-3/nitrolite` TypeScript SDK into a React application. Use it as a reference for building your own Yellow-Network-powered app. ## Quick Start @@ -14,10 +14,10 @@ Opens at `http://localhost:3000`. Requires MetaMask. ## Install the SDK ```bash -npm install @erc7824/nitrolite viem decimal.js +npm install @layer-3/nitrolite viem decimal.js ``` -- **`@erc7824/nitrolite`** — the state channel SDK +- **`@layer-3/nitrolite`** — the state channel SDK - **`viem`** — Ethereum wallet and signing primitives - **`decimal.js`** — precise decimal arithmetic for token amounts @@ -85,7 +85,7 @@ import { Client, withBlockchainRPC, ChannelDefaultSigner, -} from '@erc7824/nitrolite'; +} from '@layer-3/nitrolite'; import { createWalletClient, custom } from 'viem'; import { mainnet } from 'viem/chains'; @@ -102,7 +102,7 @@ const txSigner = new WalletTransactionSigner(walletClient); // Connect const client = await Client.create( - 'wss://clearnode-v1-rc.yellow.org/ws', + 'wss://clearnode-sandbox.yellow.org/v1/ws', stateSigner, txSigner, withBlockchainRPC(11155111n, 'https://ethereum-sepolia-rpc.publicnode.com'), @@ -233,7 +233,7 @@ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { ChannelSessionKeyStateSigner, getChannelSessionKeyAuthMetadataHashV1, -} from '@erc7824/nitrolite'; +} from '@layer-3/nitrolite'; // 1. Generate a temporary key pair const privateKey = generatePrivateKey(); @@ -276,7 +276,7 @@ const sessionSigner = new ChannelSessionKeyStateSigner( const txSigner = new WalletTransactionSigner(walletClient); const sessionClient = await Client.create( - 'wss://clearnode-v1-rc.yellow.org/ws', + 'wss://clearnode-sandbox.yellow.org/v1/ws', sessionSigner, txSigner, withBlockchainRPC(11155111n, 'https://ethereum-sepolia-rpc.publicnode.com'), @@ -290,7 +290,7 @@ Now `sessionClient` signs off-chain state updates with the session key — no wa Submit a new version with empty assets to revoke a session key: ```ts -import { packChannelKeyStateV1 } from '@erc7824/nitrolite'; +import { packChannelKeyStateV1 } from '@layer-3/nitrolite'; const existing = await client.getLastChannelKeyStates(address, sessionKeyAddress); const latest = existing[0]; @@ -328,7 +328,7 @@ import { withHandshakeTimeout, withPingInterval, withErrorHandler, -} from '@erc7824/nitrolite'; +} from '@layer-3/nitrolite'; const client = await Client.create( wsUrl, @@ -362,6 +362,5 @@ src/ ## Resources -- [Nitrolite Documentation](https://erc7824.org/quick_start) -- [GitHub Repository](https://github.com/erc7824/nitrolite) +- [GitHub Repository](https://github.com/layer-3/nitrolite) - [Viem Documentation](https://viem.sh) diff --git a/sdk/ts/examples/example-app/package-lock.json b/sdk/ts/examples/example-app/package-lock.json index 64a35a32b..48ff3c2ac 100644 --- a/sdk/ts/examples/example-app/package-lock.json +++ b/sdk/ts/examples/example-app/package-lock.json @@ -34,7 +34,7 @@ }, "../..": { "name": "@yellow-org/sdk", - "version": "1.1.0", + "version": "1.2.0", "license": "MIT", "dependencies": { "abitype": "^1.2.3", diff --git a/sdk/ts/examples/example-app/package.json b/sdk/ts/examples/example-app/package.json index 1fd308e15..719e7eb1f 100644 --- a/sdk/ts/examples/example-app/package.json +++ b/sdk/ts/examples/example-app/package.json @@ -17,11 +17,11 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "decimal.js": "^10.4.3", - "lucide-react": "^0.563.0", + "lucide-react": "^0.564.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "tailwind-merge": "^3.4.0", - "viem": "^2.39.3" + "tailwind-merge": "^3.4.1", + "viem": "^2.46.1" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/sdk/ts/examples/example-app/src/App.tsx b/sdk/ts/examples/example-app/src/App.tsx index c419e7ed1..4c481b8ed 100644 --- a/sdk/ts/examples/example-app/src/App.tsx +++ b/sdk/ts/examples/example-app/src/App.tsx @@ -10,7 +10,7 @@ import type { AppState, ClearnodeConfig, NetworkConfig, SessionKeyState, StatusM import { formatAddress } from './utils'; const CLEARNODES: ClearnodeConfig[] = [ - { name: 'YN Testnet', url: 'wss://clearnode-v1-rc.yellow.org/ws' }, + { name: 'YN Testnet', url: 'wss://clearnode-sandbox.yellow.org/v1/ws' }, ]; const NETWORKS: NetworkConfig[] = [ @@ -272,9 +272,10 @@ function App() { const currentNetwork = NETWORKS.find(n => n.chainId === appState.selectedChainId); return ( -
+
+ {/* Header */} -
+
@@ -301,14 +302,14 @@ function App() { {/* Network indicator */} {currentNetwork && ( - + {currentNetwork.name} )} {/* Wallet */} {autoConnecting ? ( -
+
Connecting...
@@ -321,7 +322,7 @@ function App() { ) : (
-
+
{formatAddress(appState.address)}
@@ -343,7 +344,7 @@ function App() { {status && setStatus(null)} />} {/* Main Content */} -
+
{appState.connected && appState.client && appState.address && walletClient ? (
+ ) : !autoConnecting && appState.address && !appState.connected ? ( +
+
+

+ Connection Failed +

+

+ Wallet connected as {formatAddress(appState.address)} but the clearnode is unreachable. +

+
+ + +
+
+
) : null} {/* Footer */} -